0-1背包
有n种物品,第i种物品的体积为V[i],重量为W[i],选一些物品放到容量为c的背包里,使得背包内的物品在总体积不超过C的前提下重量尽量大。
用d(i,j)表示当前处于第i层,背包的剩余重量为j,通俗点说就是把 i i+1 i+2 .....n 个物品装到容量为j的背包中的最大总重量。
所以我们得到的状态转移方程为:
d(i+1,j) ( 0=<j<v[i])
d(i,j)= max( d(i+1,j) , d(i+1,j-v[i])+w[i] ) (j>v[i])
最终的答案为 d(1,c)
#include<iostream> #define MAX 100 using namespace std; int main(){ int n,c; int d[MAX][MAX]; int v[MAX],w[MAX]; while(cin>>n>>c){ memset(d,0,sizeof(d)); for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; for(int i=n;i>=1;i--){ for(int j=0;j<=c;j++){ d[i][j]=(i==n ? 0: d[i+1][j]) ; if(j>=v[i]) d[i][j]=max(d[i][j],d[i+1][j-v[i]]+w[i]); } } cout<<d[1][c]<<endl; } return 0; }
聪明的读者也许看出来了,还有另外一种对称的状态的定义:
用f(i,j) 表示把前i个物品装到容量为j的背包中的最大重量,其转移方程也不难得出
f(i,j)=max(f(i-1,j),f(i-1,j-v[i])+w[i])
最终的答案为 f(n,c);
于是上面的代码就可以改写为
for(int i=1;i<=n;i++){ for(int j=0;j<=c;j++){ f[i][j]=(i==1?0:f[i-1][j]); (if j>=v[i] )f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); } }
看上去两种方式是完全对称的,但是还是有细微差别的,新的允许边读边写,而不必把w,v保存下来。
for(int i=1;i<=n;i++){ for(int j=0;j<=c;j++){ cin>>v>>w; f[i][j]=(i==1?0:f[i-1][j]); f[i][j]=max(f[i][j],f[i-1][j-v]+w); } }
更为奇妙的是还可以把数组f变成一维:
for(int i=1;i<=n;i++){ cin>>v>>w; for(int j=c;j<=0;j--){ if(j>=v){ f[j]=max(f[j],f[j-v]+w); } } }
值得注意的是j一定要逆序。
如果是顺序的话,在一次i的循环中就会利用前面的结果,从而导致不是在这一次中放一个物品,而是多个的假象,读者可以自行试试。
在递推法中,如果计算顺序很特殊,而且计算新状态所用到的原状态不多的话,可以尝试用滚动数组来减少内存开销。