完全背包
完全背包是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的取法,都可以通过这些新物品的组合得到,所以既压缩了数量,又能得到最优解
参考资料:
- 王道机试指南;