1.0/1背包
var v,p:array[0..1000] of longint;
f:array[0..100000] of longint;
n,m,i,j:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a); exit(b);
end;
begin
readln(n,m);
for i:=1 to n do readln(v[i],p[i]);
for i:=1 to n do
for j:=m downto v[i] do
f[j]:=max(f[j],f[j-v[i]]+p[i]);
writeln(f[m]);
end.
2.完全背包
var v,p:array[0..1000] of longint;
f:array[0..100000] of longint;
n,m,i,j:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a); exit(b);
end;
begin
readln(n,m);
for i:=1 to n do readln(v[i],p[i]);
for i:=1 to n do
for j:=v[i] to m do
f[j]:=max(f[j],f[j-v[i]]+p[i]);
writeln(f[m]);
end.
note:0/1背包和完全背包中有时要求背包必须装满,其实等价于只能从F[0]转移过来.
3.多重背包
先贴上朴素代码:
var v,p,c:array[0..1000] of longint;
f:array[0..100000] of longint;
n,m,i,j,k:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a); exit(b);
end;
begin
readln(n,m);
for i:=1 to n do readln(v[i],p[i],c[i]);
for i:=1 to n do
for k:=1 to c[i] do
for j:=m downto v[i] do
f[j]:=max(f[j],f[j-v[i]]+p[i]);
writeln(f[m]);
end.
复杂度O(NMC),联赛不太可能考这么裸的.
以下是几种优化的算法.
1.二进制拆包:
var v,p:array[0..1000,0..20] of longint;
f:array[0..100000] of longint;
c:array[0..1000] of longint;
n,m,i,j,k,l,vv,pp,cc:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a); exit(b);
end;
begin
readln(n,m);
for i:=1 to n do
begin
readln(vv,pp,cc);
l:=trunc(ln(cc)/ln(2));
for j:=0 to l-1 do
begin
v[i,j]:=vv*(1<<j);
p[i,j]:=pp*(1<<j);
end;
c[i]:=l-1;
if cc-1<<l+1>0 then
begin
inc(c[i]);
v[i,l]:=vv*(cc-1<<l+1);
p[i,l]:=pp*(cc-1<<l+1);
end;
end;
for i:=1 to n do
for k:=0 to c[i] do
for j:=m downto v[i,k] do
f[j]:=max(f[j],f[j-v[i,k]]+p[i,k]);
writeln(f[m]);
end.
note:
设一个物品的个数为C.
设k是满足C-2^(k+1)+1>0的最大整数.
则将物品拆分为系数分别为1,2,4,...,2^k,C-2^(k+1)+1的物品,当然,要乘上体积、价值.
为什么可以这样拆?
因为上面的系数可以组成0~C的所有数.简单证明如下:
首先,1,2,4,...,2^k可以组合出0~2^(k+1)-1的所有整数.
而C-2^(k+1)+1<=2^(k+1)-1,否则将不满足"k是满足C-2^(k+1)+1>0的最大整数"
所以2^(k+1)~C的整数也全部可以被表示.
复杂度O(NM∑logCi),可以对付一般的题目.
2.单调队列优化
type rec=record
v,p:longint;
end;
var f:array[0..10001] of longint;
q:array[0..100000] of rec;
n,m,i,j,h,t,v,p,c,mc,r:longint;
function min(a,b:longint):longint;
begin
if a>b then exit(b); exit(a);
end;
procedure insert(pos,val:longint);
begin
while (h<=t)and(q[t].v<=val) do dec(t);
inc(t);
q[t].p:=pos;
q[t].v:=val;
end;
begin
readln(n,m);
for i:=1 to n do
begin
readln(v,p,c);
mc:=min(c,m div v);
for r:=0 to v-1 do
begin
h:=1; t:=0;
for j:=0 to (m-r) div v do
begin
insert(j,f[j*v+r]-j*p);
if j-q[h].p>mc then inc(h);
f[j*v+r]:=q[h].v+j*p;
end;
end;
end;
writeln(f[m]);
end.
note:
我们先来观察普通的方程(对于一个物品):
f[j]=max{f[j-k*v]+k*p} (1<=k<=c,v<=j<=M)
复杂度为O(NMC).
怎么优化?
考虑决策点j,将其按模v的余数分类(分类转移并不影响决策的正确性).
对于每一类j,设p=[j/v],r=j-p*v (即j mod v),则j=p*v+r
那么j可以从l=q*v+r转移过来,其中q>=0 & p-c<=q<=p.
随着j的增加,p也增加,q的下界也在增加(单调性!).
将方程改写一下:
f[p*v+r]=max{f[q*v+r]+(p-q)*v} (q>=0 & p-c<=q<=p)
即f[p*v+r]=max{f[q*v+r]-q*v}+p*v
(这里r是人工枚举的,视为常量)
算法已经出来了:
for 每一个物品 do
for r=0 to v-1 do
{
清空队列.
for j=0 to lim do //lim为j的上界,lim*v+r<=M
{
将上一个阶段的值插入队列;
删除失效决策点;
取队头元素更新;
}
}
复杂度为O(NM),应该不会被卡掉..
多重背包的优化告一段落.
再写一种叫做数组标记的算法(建议先看POJ1742).
对于POJ1742这样的题目,数组标记具有代码短,易编写,易调试,效率高等等优点.
核心思想是用一个count数组记录体积为j时,当前物品使用了多少个.具体见代码:
var f:array[0..100001] of boolean;
count:array[0..100001] of longint;
a,c:array[0..101] of longint;
n,m,i,j,ans:longint;
begin
while not seekeof do
begin
readln(n,m);
if n+m=0 then break;
fillchar(f,sizeof(f),0);
f[0]:=true;
ans:=0;
for i:=1 to n do read(a[i]);
for i:=1 to n do read(c[i]);
readln;
for i:=1 to n do
begin
fillchar(count,sizeof(count),0);
for j:=a[i] to m do
if(not f[j])and(f[j-a[i]])and(count[j-a[i]]<c[i]) then
begin
f[j]:=true;
count[j]:=count[j-a[i]]+1;
end;
end;
for i:=1 to m do
if f[i] then inc(ans);
writeln(ans);
end;
end.
复杂度O(NM)
这种算法相当优秀,但是有使用的局限性(不然要单调队列干什么).
好了,基础的背包问题就整理到这了.
参考资料:
http://www.cppblog.com/MatoNo1/archive/2011/07/05/150231.aspx?opt=admin
http://hi.baidu.com/sy2006ppkdc/blog/item/301e451f3169178686d6b621.html
http://www.cppblog.com/Onway/articles/122042.html
浙公网安备 33010602011771号