詳細的參見揹包九講,地址:http://download.csdn.net/detail/u014007510/7258089
i表示前i件物品,j表示空間大小
01揹包:每件物品可放一次,狀態轉移方程:
二維的: f[i][j]=max(f[i-1][j],f[i-1][j-cost[i]]+worth[i]),放與不放中取最大的
先寫成i行j列的矩陣;
觀察到:f[i][j]的取值只與f[i-1]層的數據有關,每次更新第j列的數據就行了,那麼可以將二維的動態轉移方程寫成一維的形式;
f[j]=max(f[j],f[j-cost[i]]+worth[i]) ①
ps: 其實就是把f裏面的二維i都去掉,對比上面的方程會發現神奇之處,暫且留個小問題下面講
01揹包是核心思想必須要完全理解它。
完全揹包:
二維的: f[i][j]=max(f[i-1][j],f[i-k][j-k*cost[i]]+k*worth[i]) 或者 f[i][j]=max(f[i-k][j-k*cost[i]]+k*worth[i])
ps: 轉換成01揹包來做了,k的可能取值:0<=V-k*cost[i]<=V,枚舉k就可以了。
上面的動態轉移方程看起來比較複雜,而且也不太美,有沒有更簡潔的方程呢?
簡單的二維方程: f[i][j]=max[f[i-1][j],f[i][j-cost[i]]+worth[i]]
這個方程比較有意思,f[i-1][j]表示在j的空間中放前i-1件的物品可達到的最大價值,f[i][j-cost[i]]+worth[i]表示在j-cost[i]的空間裏放前i件物品可達到的最大價值的基礎上 放一個第i件物品,f[i][j]的取值必然是由這兩種情況得來的!其實這個方程就是:在可能放多個與不放中取最大的。
可能放多個的表示方法:就是在可能已放(即f[i][j-cost[i]])的基礎上再放一個worth[i]。(講的清楚吧^_^)
一維的: f[j]=max(f[j],f[j-cost[i]]+worth[i]) ②同樣是在放與不放中取最大的
呵呵,看到這兒請比較①②的,看起來是完全一樣的,那麼應該怎麼區分他們呢?
注意:①中max裏的f[j]表示的是放前i-1件的最大值,②中max裏的f[j]表示的是放前i件的最大值;
實現:
①:
void ZO(int V,int C,int W)//V表示整個空間的大小,C指第i種物品的大小,W表示第i種物品的價值
{
for(int v=V;v>=C;v--)//倒序更新,保證F[v]是前i-1件的最大值
{
F[v]=max(F[v],F[v-C]+W);
}
}
②對每個i執行
void CO(int V,int C,int W)
{
for(int v=C;v<=V;v++)//順序更新,保證F[v]是前i件的最大值
F[v]=max(F[v],F[v-C]+W);
}
多重揹包:
二維的: f[i][j]=max(f[i-1][j],f[i-k][j-k*cost[i]]+k*worth[i]) 或者 f[i][j]=max(f[i-k][j-k*cost[i]]+k*worth[i])
ps: 和完全揹包類似,0<=V-k*cost[i]<=V且0<=k<=num[i]
有了前面的基礎可以想到:把第i件做多取num[i]件理解爲有num[i]件價值爲worth[i]的物品,再用01揹包做就行了。
但是有沒有更高效的方法呢?
先看這個問題:一個數字n可以寫成n=2^0+2^1+2^3+...+2^i+k,1<=2^i<=n,0<=k<=n-2^i。
以19爲例:寫成10011>1111,進而19=10011=1+10+100+1000+(10011-1-10-100-1000)=1+2+4+8+4
通過上面的簡單非嚴格說明可以知道,n=2^0+2^1+2^3+...+2^i+k,1<=2^i<=n,0<=k<=n-2^i
回到原問題,
將num[i]件物品分解爲2^0件的組合體+2^1件的組合體+2^3件的組合體+...+2^件的組合體i+k件的組合體。這種方法比前一種好多了。
實現:
對每個i
void MU(int V,int C,int W,int M)
{
if(C*M>=V)//相當於完全揹包
CO(V,C,W);
else
{
int k=1;
while(k<M)
{
ZO(V,k*C,k*W);//分解爲01揹包
M=M-k;
k=2*k;
}
ZO(V,M*C,M*W);
}
}
混合揹包問題:
其實就是多重揹包問題,不過num[i]的取值有區別罷了:屬於01揹包的num[i]=1,屬於完全揹包的num[i]=(V/cost[i]+1)。
當然也可以這麼做
for i=1..N
if 第i件物品屬於01揹包
ZO(V,cost[i],worth[i])
elseif 第i件物品屬於完全揹包
CO(V,cost[i],worth[i])
else if 第i件物品屬於多重揹包
MU(V,cost[i],worth[i],num[i])
最後初始化問題:
①恰好裝滿:f[0]=0,f[1,2,3.....n]=-∞,理解:對0,1,2...n大的空間都什麼也不裝,只有0可以裝,其他都是非法的
②不用恰好裝滿:f[0,1,2...n]=0,理解,對0,1,2...n大的空間都什麼也不裝,都是合法的,都可爲0