【问题描述】
有N种物品和一个容量为w的背包,第i种物品最多有m[i]件可用,每件容量是c[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
【输入】
第一行 正整数n和w
以下n行,每行三个正整数为物品的容量、价值和件数,中间用空格隔开。
【输出】
最大价值总和。
【数据规模】
1<= n<=100, w<=10000, 1<=m[i]<=10000, 1<=c[i],v[i]<=100
分析:
方法1,朴素算法:f[i,j]表示前i种物品装入容量为j的背包中所能得到的最大价值(0<=k<=min(m[i],j div v[i]))。
f[i,j]=max{f[i-1,j-k*c[i]]+k*v[i]} ,0<=k<=min(m[i],j div v[i])。
时间复杂度O(n*V*∑m[i])
方法2,转换为01背包,利用二进制拆分
假设第i种物品有m[i]件,则可以第i种物品看成m[i]件01背包中的物品,则得到了物品数为Σm[i]的01背包问题,直接求解.时间复杂度:O(V*Σ m[i])。
二进制拆分:
m[i]=1+2+4+...+2^k+a (0<=a<2(k+1),a=n-2^(K+1)+1)
由于1,2....,2^k可以表示0~2^(K+1)-1中的所有整数(由K位二进制表示),则1,2,4,...,2^k,a可以表示出0~m[i]的所有整数(a与0~2^(K+1)-1的所有整数相加,可以表示出2^(K+1)~n的所有整数)。
m[i]可以分解出log(m[i])+1个背包,则时间复杂度为O(V*∑log(m[i]))
方法3,单调队列
方法1中f[i,j]=max{f[i-1,j-k*c[i]]+k*v[i]} ,0<=k<=min(m[i],j div v[i])。
令a[j]=f[i-1,j*c[i]],k=m[i],假设当前重量为c[i]的整数倍,即j mod c[i]=0 则有
f[i,(j+k)*c[i]]=max{f[i-1,(j+k)*c[i]-k*c[i]]+k*v[i],f[i-1,(j+k)*c[i]-(k-1)*c[i]]+(k-1)*v[i],...,f[i-1,(j+k)*c[i]+(k-k)*c[i]]+(k-k)*v[i]
=max{f[i-1,j*c[i]]+k*v[i],f[i-1,(j+1)*c[i]]+(k-1)*v[i],...f[i-1,(j+k)*c[i]]}
=max{a[j]+k*v[i],a[j+1]+(k-1)*v[i],...,a[j+k]}
这样还是不方便计算,令b[j]=a[j]-j*v[i]
则有f[i,(j+k)*c[i]]=max{b[j]+(j+k)*v[i],b[j+1]+(J+K)*v[i],...,b[j+k]+(j+k)*v[i]}
=max{b[j],b[j+1],...,b[j+k]}+(j+k)*v[i]
这样就可以使用单调队列来求解了,时间复杂度O(nw)。
(注意,前面的公式推导是假设当前重量为c[i]的整数倍,程序实现时要考虑到所有情况,如,不是c[i]整数倍情况的处理。
余数有0,1,2,......c[i]-1种情况,c[i]可以选0,1,......m[i]件)。
for i:=1 to n do
for t:=0 to c[i]-1 do
begin
..................
end;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //f[i,(j+k)*c[i]]=max{b[j],b[j+1],...,b[j+k]}+(j+k)*c[i] 2 uses math; 3 const 4 maxn=110; 5 maxw=11000; 6 var 7 n,w:longint; 8 m,c,v:array[0..maxn] of longint; 9 f:array[0..maxn,0..maxw] of longint; 10 procedure init; 11 var i:longint; 12 begin 13 assign(input,'dcb.in');reset(input ); 14 readln(n,w); 15 for i:=1 to n do readln(c[i],v[i],m[i]); 16 end; 17 procedure main; 18 var 19 head,tail,x,i,j,t:longint; 20 qd,qb:array[0..maxn] of longint; 21 begin 22 for i:=1 to n do 23 for t:=0 to c[i]-1 do //枚举所有可能的余数 24 begin 25 head:=0;tail:=0; 26 fillchar(qd,sizeof(qd),0); 27 fillchar(qb,sizeof(qb),0); 28 for j:=0 to (w-t) div c[i] do //j*c[i]+t<=w;枚举余数为t,重量为j*c[i]+t时的情况 29 begin //维护队列中元素个数<=c[i]的单调队列 30 x:=f[i-1,j*c[i]+t]-j*v[i];//计算b[j] 31 while (head<tail)and(qd[tail]<=x) do dec(tail);//保持队列的单调递减 32 inc(tail);qb[tail]:=j;qd[tail]:=x;//b[j]与下标j分别入队 33 f[i,j*c[i]+t]:=qd[head+1]+j*v[i];//队首元素(队列中最大的元素)+j*v[i], 34 if qb[head+1]=j-m[i] then inc(head);//如果正好是第j-m[i]个下标则出队,即构成0~k的循环了。 35 end; 36 end; 37 end; 38 begin 39 assign(output,'dcb2.out');rewrite(output); 40 init; 41 main; 42 writeln(f[n,w]); 43 close(output); 44 end.
(注意也可以用一维数组实现)