完全背包
完全背包是01背包的进阶版。
题目是一个体积为V的背包,要装N种物品,每种物品有体积c,价值w两种属性, 要求装入物品总价值最大,与01背包唯一不同的是:每种背包可以取任意数量。
同样,用i表示第i个物品,这个物品的体积为 ci ,价值为 wi ,j表示当前已装入背包的物品总体积, dp[j] 表示背包已装物品总体积为j时的物品价值(不确定是否遍历完全部物品)。
这类题目不能用贪心算法的原因是,并不是价值越高的物品越多越好,必须还要考虑体积因素。
我们来分析一下,虽然每种物品可以取任意数量,但是背包总体积是固定的,体积为 ci 的物品最多装 Vci 个,那么将其视为 Vci 个体积均为 ci ,价值均为 wi 的不同种物品,就可以转化到01背包的问题上来,这种思路和下面多重背包的解法是一致的,与01背包的区别无非是保存物品属性list数组从n个变成了 ∑ni=0Vci 个。
我们有一种更好的解法,其时间复杂度达到了 O(n∗s) :在01背包的一维数组解法中,我们是这样写的:
for(int i = 0; i < n; ++i)
for(int j = V; j >= list[i].c; --j)
dp[j] = max{dp[j-list[i].c]+list[i].w, dp[j]};
即逆序遍历,这是因为任意一件物品仅有放入或者不放入两种结果,我们在讨论是否放入第i个物品时,依据的是是否放入第i-1个物品的结果,第i个物品必须是还没有讨论过的,比如,已经放进去了,就不可能再放一遍。但是我们反过来想,如果当前我们在讨论是否放入第i个物品的时候,第i个物品已经被讨论过了,那不就是意味着第i个物品不仅有放入或者不放入的结果,还有放入1件、放入2件、…、放入 Vci 件的结果。
因此,我们只要将01背包遍历顺序改为正序遍历,就变成了完全背包的解法
for(int i = 0; i < n; ++i)
for(int j = list[i].c; j <= V; ++j)
dp[j] = max{dp[j-list[i].c]+list[i].w, dp[j]};
一个简单的优化做法是,当 ci<=cj,wi>=wj ,那么物品j就不要考虑了,因为不论什么情况,取物品i都比取物品j好。
多重背包
一个体积为V的背包,要装N种物品,每种物品有数量k,体积c,价值w三种属性, 要求装入物品总价值最大。
与完全背包想比,多重背包就是限制了数量。上文讨论完全背包时也提到过,把 ki 件物品i,看成 ki 件体积均为 ci ,价值均为 wi 的不同种物品,就可以转化到01背包上去。
n个物品变成了 ∑ni=0ki 个物品,算法的时间复杂度变为 O(S∗∑ni=0ki) 。可以看出, ∑ni=0ki 越大,算法的时间复杂度越高,我们要想办法压缩这个数量。4个体积为 ci ,价值均为 wi 的物品x,可以看成一个体积为 4∗ci ,价值为 4∗wi 的物品y,这样的做法虽然压缩了数量,但是不一定得到最优解,比如原解法中,物品x放入2件的时候才有最优解,这里我们却提供不了2个物品x(已经合并成物品y)。
常用的压缩数量的做法是,用二进制分解k(物品的数量)将其分为若干组,每组视为一个新物品,其每组物品包含的个数是: 1,2,4,...,k−2c+1 ,且 k 是满足 k−2c+1>0‘ 的最大整数。这样的好处是,任意一种小于等于k的取法,都可以通过这些新物品的组合得到,所以既压缩了数量,又能得到最优解
参考资料:
- 王道机试指南;