引入
所谓多重背包问题,基本长一个样:一个背包,承量有限为W,有n种物体,第i种物体,价值Vi,占用重量为 Wi,且有Ci件,选择物品若干放入背包,使得总重量不超过背包的承重,求最大总价值。
解法
我们可以先从一般的01背包的解法入手:倘若用01背包的解法解这道题,很容易想到,可以将其中的一种物品分为Ci个相同的物品,物品的总个数就是C1+C2+C3+....+Cn,在以01背包的方法进行处理,我们令物品的总个数为S,它的时间复杂度为O(S*W),而通常,S接近于n*n,显然,这样处理并不优。
于是,我们来尝试优化,而他的转移的时间复杂度是O(1)的,所以我们只能优化状态枚举。显而易见的一点是,W那一维无法优化,我们就锁定了S那一维。基于01背包的思想,我们应该减少总个数,而物品种数是不变的,所以尝试去减少每种物品分为多个物品的个数,即使用尽量少的物品构成Ci个相同物品能构成的所有状态。我们想到用二的幂数可以表示出任意正整数,所以可以把1,2,4,8,....,2^k(2^k<Ci<=2^k+1)个相同的物品分别看作一个物品,然后用这k个物品去代替Ci个相同的物品,可以高效的处理此类问题。但在分作k个物品时,还存在一个小问题,我们举个例子来看:设Ci=18,我们分作1,2,4,8,16共五个,然而以全取为例,1+2+4+8+16=31>18;所以,为了保证其正确性,我们将16替换为3 (18-1-2-4-8=3)。处理好这个问题就可以了。此时时间复杂度为O(n*logn*W),这就是多重背包的logn解法。
当然,还有O(n*W)的解法——单调队列优化,我在此次就不提了,但很快会另补一篇关于O(n*W)单调队列优化的博客,有兴趣的可以留意一下,或去找其他人的博客。
代码
这个代码其实非常容易构建,只要理解后,动动笔,注意一下细节就可以了,此处只给出核心的伪代码(分作logCi个物品):
for (int j=1;;j*=2){
if (C[i]>=j){
w[num]=j*W;
v[num]=j*V;
Ci-=j;
num++;
}
else{
w[num]=C[i]*W;
v[num]=C[i]*V;
num++;
break;
}
}
接下来只要加一个01背包即可。
参考
《背包九讲》,《算法导论》及“lfw的课堂笔记”。