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的循环中就会利用前面的结果,从而导致不是在这一次中放一个物品,而是多个的假象,读者可以自行试试。

在递推法中,如果计算顺序很特殊,而且计算新状态所用到的原状态不多的话,可以尝试用滚动数组来减少内存开销。

 

posted @ 2015-11-12 21:41  咸咸的告别  阅读(194)  评论(0编辑  收藏  举报