最基礎的揹包問題,關鍵是每一個物品只要一件,基本的狀態轉移方程就是:f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+v[i]}
有個須要注意的地方是:要求剛好裝滿揹包,那麼在初始化時除了f[0]爲0其它f[1..V]均設爲-∞,這樣就能夠保證最終獲得的f[N]是一種剛好裝滿揹包的最優解。若是並無要求必須把揹包裝滿,而是隻但願價格儘可能大,初始化時應該將f[0..V]所有設爲0。能夠這麼思考,由於要求裝滿,一開始只要f[0]時知足「裝滿」,這時爲0,其他的都沒有合法解,所以爲-∞。通常作法時間複雜度和空間複雜度都是O(N*V),能夠將空間複雜度降到O(V)
16512821 |
2016-03-11 15:20:45 |
Accepted |
2602 |
78MS |
5732K |
633 B |
G++ |
seasonal |
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define max(a,b) a>b?a:b
int f[1050][1050];
int value[1050], volume[1050];
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int n, v,i,j;
memset(f, 0, sizeof(f));
scanf("%d%d", &n, &v);
for (i = 1; i <= n; i++)
scanf("%d", &value[i]);
for (i = 1; i <= n; i++)
scanf("%d", &volume[i]);
for (i = 1; i <= n; i++)
for (j = 0; j <= v; j++)
{
if (volume[i] <= j)//假如當前的能放入揹包才考慮狀態轉移方程
f[i][j] = max(f[i - 1][j], f[i - 1][j - volume[i]] + value[i]);
else //不然直接等於上一個,至關於這個不放
f[i][j] = f[i - 1][j];
}
printf("%d\n", f[n][v]);
}
return 0;
}
空間複雜度O(V):
要點就是利用f[v]儲存上一個i-1的值,此時f[v]與f[v-w[i]]比較時其實已是在比較i-1時的值。注意要倒序,使j從V…0,由於此時f[j]存儲的實際是f[i-1][j]的值,若是從小到大算,假設要求f[5]先要求出f[3],求出f[3]後f[3]儲存的就是f[i][3],而計算f[5]須要的是f[i-1][3],因此要倒序保證小的值不會被更新。
16513374 |
2016-03-11 16:28:38 |
Accepted |
2602 |
31MS |
1424K |
538 B |
G++ |
seasonal |
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define max(a,b) a>b?a:b
int f[1050];
int value[1050], volume[1050];
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int n, v,i,j;
memset(f, 0, sizeof(f));
scanf("%d%d", &n, &v);
for (i = 1; i <= n; i++)
scanf("%d", &value[i]);
for (i = 1; i <= n; i++)
scanf("%d", &volume[i]);
for (i = 1; i <= n; i++)
for (j = v; j >= volume[i]; j--)//這裏是倒序,使f[j]與f[i-1][j],f[j-volume[i]]與f[i-1][j-volume[i]]相互對應
f[j] = max(f[j], f[j - volume[i]] + value[i]);//f[j]至關於存了前一個i-1的f值,此時比較與本來二維一個道理
printf("%d\n", f[v]);
}
return 0;
}
二:徹底揹包問題
與01揹包最大的不一樣在於物品的數量能夠是無窮個,因此基本的二維狀態轉移方程應該是這樣的:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]},0<=k*c[i]<=v
這裏用二維實際上是比較麻煩的,由於涉及到k很差寫,用一維的方法反而比較好寫:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
但這裏與01揹包不一樣之處在於它的v是升序的,01揹包中降序是爲了確保物品只加入一個,使上一個i-1的狀態(沒有加物品)不會被更新掉,而徹底揹包中由於物品有無窮個,反而須要不停更新,使上一個i-1的狀態也能夠是加過了物品,因此必需要用升序。
例子:POJ1384&&HDU1114
題意:
往儲蓄罐裏存硬幣,已知空儲蓄罐和放滿後的重量,給出n種硬幣的價值和重量,要求裝滿儲蓄罐可能的最小价值和
要點:
首先是要求必須裝滿,這裏就是前面01揹包的初始化問題,DP[0]=0,其他的均賦值爲+∞,而後用徹底揹包求最小值。狀態轉移方程爲:dp[j] = min(dp[j], dp[j - w[i]] + p[i])
16536210 |
2016-03-13 10:29:57 |
Accepted |
1114 |
78MS |
1460K |
697 B |
G++ |
seasonal |
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define INF 0xfffffff
#define min(a,b) a>b?b:a
int dp[10005],w[505],p[505];
int main()
{
int empty, full,T;
int i, j;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &empty, &full);
int v = full - empty;
int n;
scanf("%d", &n);
for (i = 0; i < n; i++)
scanf("%d%d", &p[i], &w[i]);
for (j = 0; j <= v; j++)
dp[j] = INF; //注意初始化
dp[0] = 0;
for (i = 0; i < n; i++)
for (j = w[i]; j <= v; j++)//一維的徹底揹包
dp[j] = min(dp[j], dp[j - w[i]] + p[i]);
if (dp[v] == INF)
printf("This is impossible.\n");
else
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]);
}
return 0;
}
三:多重揹包
不一樣之處在於物品規定了個數,第i個物品有c[i]個。通常使用二進制拆解,將複雜度降爲O(V*Σlog n[i]),方法以下:
針對第i個物品有c[i]個,咱們能夠將c[i]拆成1,2,4,...,2^(k-1),n[i]-2^k+1,這樣想放1~n[i]中任意個均可以用前面的係數表示,如9=1+2+4+2,這樣同時將對應的物品價值和重量乘係數做爲一個新的物品,同時這個物品只有一個,就是01揹包。還有若是一開始物品數大於總重量/物品質量,就直接能夠視爲無窮個,由於反正都是用不光,就轉化爲徹底揹包問題。
僞代碼:
procedure MultiplePack(cost,weight,amount)
if cost*amount>=V
CompletePack(cost,weight)
return
integer k=1
while k<num
ZeroOnePack(k*cost,k*weight)
amount=amount-k
k=k*2
ZeroOnePack(amount*cost,amount*weight)
例子:HDU2191
16570781 |
2016-03-16 14:37:38 |
Accepted |
2191 |
0MS |
1432K |
985 B |
G++ |
seasonal |
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define max(a,b) a>b?a:b
int v[1500], w[1500], c[1500],dp[1500];
int n, m;
void one_pack(int cost, int weight)//01揹包
{
for (int j = m; j >= cost; j--)
dp[j] = max(dp[j], dp[j - cost] + weight);
}
void complete_pack(int cost, int weight)//徹底揹包
{
for (int j = cost; j <= m; j++)
dp[j] = max(dp[j], dp[j -cost] + weight);
}
void multiple_pack(int cost, int weight, int count)
{
int i;
if (cost*count >= m)//若是一開始就count*cost>=m,能夠當作無限種也就是徹底揹包
{
complete_pack(cost, weight);
}
else
{
int k=1;
while (k < count)
{
one_pack(k*cost, k*weight);//至關於01揹包
count -= k;
k *= 2; //利用二進制思想能夠表示1-count的全部值
}
one_pack(count*cost, count*weight);//最後處理剩下的個數
}
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
memset(dp, 0, sizeof(dp));
scanf("%d%d", &m, &n);
for (int i = 0; i < n; i++)
scanf("%d%d%d", &w[i], &v[i], &c[i]);
for (int i = 0; i < n; i++)
multiple_pack(w[i], v[i], c[i]);
printf("%d\n", dp[m]);
}
return 0;
}