多重背包:
有N种物品和一个体积为V的背包。第i种物品最多有n[i]件可用,每件体积是w[i],价值是v[i]。将哪些物品装入背包可使这些物品的体积总和不超过背包体积,且价值总和最大?
动态规划:
dp[i][j]表示前i种物品放入体积为j的背包中的最大价值。
状态转移方程:
dp[i][j] = max{ f[i-1][j - k*w[i]] + k*v[i]},0 <= k <= n[i]
完全按照此思路,代码为三层循环,复杂度达 O(NVΣni)。可以和完全背包一样,借鉴01背包的思想,进行优化。优化后,复杂度为O(NVΣlogni)。
优化采用二进制思想,我们考虑把第 i 种物品换成若干件物品,使得原问题中第i 种物品可取的每种策略——取 0...ni,均能等价于取若干件代换以后的物品。另外,取超过ni件的策略必不能出现。
方法是:将第 i 种物品分成若干件 01 背包中的物品,其中每件物品有一个系数。这件物品的体积和价值均是原来的体积和价值乘以这个系数。令这些系数分别为1,2,2^2......2^(k−1),ni − 2^k + 1 ,且 k 是满足ni − 2^k + 1 > 0 的最大整数。例如,如果ni为 13 ,则相应的 k = 3 ,这种最多取 13 件的物品应被分成系数分别为 1,2,4,6 的四件物品。分成的这几件物品的系数和为ni,表明不可能取多于ni件的第 i 种物品。另外这种方法也能保证对于 0......ni间的每一个整数,均可以用若干个系数的和表示。这里算法正确性的证明可以分 0...2^(k−1)和 2^k......ni两段来分别讨论得出。
证明如下:
(1) 数列1,2,2^2......2^(k - 1),n - 2^k + 1中所有元素的和为n,所以若干元素的和的范围为:[1, n]。
(2)如果正整数t <= 2^k – 1,则t一定能用1,2,4......2^(k - 1)中某几个数的和表示,这个很容易证明:我们把t 的二进制表示写出来,很明显,t可以表示成n=a0*2^0 + a1*2^1 + ...... + ak*2^(k - 1),其中ak=0或者1,表 示t的第k位二进制数为0或者1。
(3)由算法思路,n一定 <= 2^k * 2。如果t >= 2^k,设s = n - 2^k + 1,则t - s <= 2^k - 1,因而t - s可以表示成 1,2,2^2......2^(k - 1)中某几个数的和的形式,进而t可以表示成1,2,2^2......2^(k - 1),s 中某几个数的 和(加数中一定含有s)的形式。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[10000];
int w[100],v[100],num[100];//多重背包:体积 价值 数量
int w1[3500],v1[3500];//转化为01背包:体积 价值
int main(){
int N,V,index = 0;
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++){
scanf("%d%d%d",&w[i],&v[i],&num[i]);
}
for(int i = 1;i <= N;i++){
int t = num[i],k = 1;
while(t - k > 0){
t -= k;
w1[++index] = k * w[i];
v1[index] = k * v[i];
}
w1[++index] = t * w[i];
v1[index] = t * v[i];
}
memset(dp,0,sizeof(dp));
for(int i = 1;i <= index;i++){
for(int j = V;j >= w1[i];j--){
dp[j] = max(dp[j],dp[j - w1[i]] + v1[i]);
}
}
printf("%d\n",dp[V]);
return 0;
}
另一种方法:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int N,V;
int dp[10000];
int w[100],v[100],num[100];//多重背包:体积 价值 数量
void zeroOnePack(int weight,int value){
for(int i = V;i >= weight;i--){
dp[i] = max(dp[i],dp[i - weight] + value);
}
}
void completePack(int weight,int value){
for(int i = weight;i <= V;i++){
dp[i] = dp[i - weight] + value;
}
}
void multipPack(int weight,int value,int num){
if(V <= weight * num){
completePack(weight,value);
return;
}
else{
int k = 1;
while(num - k > 0){
num -= k;
zeroOnePack(k * weight,k * value);
k *= 2;
}
zeroOnePack(num * weight,num * value);
}
}
int main(){
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++){
scanf("%d%d%d",&w[i],&v[i],&num[i]);
}
memset(dp,0,sizeof(dp));
for(int i = 1;i <= N;i++){
multipPack(w[i],v[i],num[i]);
}
printf("%d\n",dp[V]);
return 0;
}