動態規劃 揹包問題基礎

2021-09-26 04:32:42 字數 4526 閱讀 9084

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<0 then

begin

inc(c[i]);

v[i,l]:=vv*(cc-1<

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  (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  (q>=0 & p-c<=q<=p)

即f[p*v+r]=max+p*v

(這裡r是人工列舉的,視為常量)

演算法已經出來了:

for 每乙個物品 do

for r=0 to v-1 do

}複雜度為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]]

複雜度o(nm)

這種演算法相當優秀,但是有使用的侷限性(不然要單調佇列幹什麼).

好了,基礎的揹包問題就整理到這了.

揹包問題(動態規劃基礎)

dp i j 表示的是要存放第i個物品時,揹包此時的容量為j,揹包內物品的總價值 weight表示物品佔據揹包的體積 dp i j dp i 1 j dp i 1 j weight i value i 狀態轉移方程我們之前看的關於dp的問題,對於每乙個元素的處理方法就是選和不選 我們的狀態轉移方程也...

動態規劃基礎 0 1揹包問題

這種揹包問題是最基礎的一類揹包問題。只要掌握的這種,後面的也就是大同小異了。因此,我在這個問題上將講得非常詳細。0 1揹包問題就是存在乙個揹包有著固定的容積m,物品有自己的質量w和價值c,當然,每乙個物品都只有一件,而且不可拆分。你的任務是 把這些東西裝入這個揹包,使裝入物品的價值最大。首先,貪心演...

動態規劃 揹包問題

給定n個物品,重量是,價值是,包的容量 承重 是w 問,放入哪些物品能使得包內價值最大 1 需要將問題轉化為子問題,通過遞迴實現,且子問題必然與父問題存在關聯 2 定義v i,j 表示為,當item取自前i個items且揹包capacity j 時,揹包問題的最優解,也即最高的價值。3 從前i個it...