背包九讲

01背包:

问题:有n件物品和体积为V的背包,每个物品有体积w[i]和价值v[i],问如何在背包内装入价值最高的物品。

显然,一种记录方法是f[i][V]表示前i件物品装入v体积中所能得到的最大价值。

转移方程:f[i][V]=max( f[i-1][V],f[i-1][V-v[i]]+w[i]);

显然,这种方程下,并不能保证f[i-1][V]一定是一个不为0的值。所以最终答案不一定是f[i][V],而是f[i][0..V]中的最大值。

问题解决到这里,时间复杂度只有o(n*V),已经无法再优化。而我们可以在空间复杂度上做文章。

现在我们考虑,f[i][V]是由f[i-1][V]和f[i-1][V-v[i]]+w[i]计算而来。

当到第i(i>1)件物品的时候,赋值之前,f[v]中存储的就是f[i-1][v]的值。但如果我们把v从0到V循环,f[V-v[i]]中存储的就不一定是f[i-1][V-v[i]],而可能是f[i][V-v[i]]了。所以必须写成:

for (int i=1; i<=n; i++)

for (int v=V; v>=w[i]; v--) //此处显然循环到w[i]即可,再小也装不进去了

转移方程:f[v]=max(f[v],f[v-w[i]]);

 

根据题目条件的不同,有两种初始化操作。

如果题目要求把背包装满,那么只需要将f[0]设成0,f[1..V]都设置成-∞就可以保证背包被装满。

如果只是要求最大价值,则f[0..V]都应该设成0。

 

完全背包:

N个物品,每个物品都可以无限使用。

可以很容易的写出状态转移方程:f[i][v]=max(f[i-1][v],f[i-1][v-k*c[i]]+k*w[i]);

其中0<=k<=V/c(i);

很容易想到的一个优化是选择“物美价廉”的产品,即可以把体积大价值低的物品直接去掉。

同时,考虑01背包的解题思路,既然每个物品最多有V/c[i]个,那么我们可以让第i个物品有V/c[i]份,这样问题就转化成了01背包。

除此之外,考虑01背包最终的解决方案,我们可以想到,只需把循环改成:

for (int i=1; i<=n; i++)

for (int v=c[i]; v<=V; v++) //此处显然循环到w[i]即可,再小也装不进去了

转移方程:f[v]=max(f[v],f[v-w[i]]);

就可以解决问题。这是因为之前要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。

完全背包的特点是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。

为了更好理解,写出二维状态的转移方程:

f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}

将这个方程用一维数组实现,便得到了上面的伪代码。

 未完待续……

posted @ 2018-05-24 14:20  竹夭公子  阅读(154)  评论(0编辑  收藏  举报