【演算法總結】01揹包與完全揹包 模板整理
三種揹包的概念區分
01揹包(ZeroOnePack): 有N件物品和一個容量為V的揹包。每種物品均只有一件。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使價值總和最大。
完全揹包(CompletePack): 有N種物品和一個容量為V的揹包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。
多重揹包(MultiplePack): 有N種物品和一個容量為V的揹包。第i種物品最多有n[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。
比較三個題目,會發現不同點在於每種揹包的數量,01揹包是每種只有一件,完全揹包是每種無限件,而多重揹包是每種有限件。
01揹包
01揹包(ZeroOnePack): 有N件物品和一個容量為V的揹包。每種物品均只有一件。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可這是最基礎的揹包問題,特點是:每種物品僅有一件,可以選擇放或不放。
用子問題定義狀態:即f[i][v]表示前i件物品恰放入一個容量為v的揹包可以獲得的最大價值。則其狀態轉移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
把這個過程理解下:在前i件物品放進容量v的揹包時,它有兩種情況:
第一種是第i件不放進去,這時所得價值為:f[i-1][v]
第二種是第i件放進去,這時所得價值為:f[i-1][v-c[i]]+w[i]
(第二種是什麼意思?就是如果第i件放進去,那麼在容量v-c[i]裡就要放進前i-1件物品)
最後比較第一種與第二種所得價值的大小,哪種相對大,f[i][v]的值就是哪種。
要求將i個物品裝進容量為j的揹包的最大價值:即取以下倆箇中的較大的一個
一個是:i-1個物品裝進容量為(j)的揹包的最大價值;
一個是:i-1個物品裝進容量為(j減去第i個物品的重量)的揹包的價值,再加上第i個物品的價值。
這裡是用二位陣列儲存的,可以把空間優化,用一位陣列儲存。
逆序!
for i=1..N for v=V..0 f[v]=max{f[v],f[v-c[i]]+w[i]};
01揹包模板
//求解將哪些物品裝入揹包可使價值總和最大
#include <iostream>
#include<cstring>
using namespace std;
const int nmax=1000;
int v[nmax];//v[i]表示第i個物品的價值value
int w[nmax];//w[i]表示第i個物品的重量weight
int dp[nmax];//總價值
int n,m;//n表示物品數量,m表示揹包容量
int main(int argc, char** argv) {//一維陣列實現的01揹包模板
while(cin>>n>>m){
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++){
cin>>w[i]>>v[i];
}
for(int i=0;i<n;i++){//遍歷n個物品
for(int j=m;j>=0;j--){//01揹包容量 逆序遍歷
if(j>=w[i]){
dp[j]=max(dp[j],(dp[j-w[i]]+v[i]));
}//第i個物體不選,dp[j]=dp[j];
//第i個物體若選 dp[j]=dp[j-w[i]]+v[i]
}
}
cout<<dp[m]<<endl;
}
return 0;
}
/*
//測試資料
//第一行:n,m
//接下來n行:w[i] v[i]
3 10
3 4
4 5
5 5
11
*/
完全揹包:
完全揹包(CompletePack): 有N種物品和一個容量為V的揹包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。
完全揹包按其思路仍然可以用一個二維陣列來寫出:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
同樣可以轉換成一維陣列來表示:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-c[i]]+w[i]}
虛擬碼如下:
順序!
想必大家看出了和01揹包的區別,這裡的內迴圈是順序的,而01揹包是逆序的。
現在關鍵的是考慮:為何完全揹包可以這麼寫?
在次我們先來回憶下,01揹包逆序的原因?是為了是max中的兩項是前一狀態值,這就對了。
那麼這裡,我們順序寫,這裡的max中的兩項當然就是當前狀態的值了,為何?
因為每種揹包都是無限的。當我們把i從1到N迴圈時,f[v]表示容量為v在前i種揹包時所得的價值,這裡我們要新增的不是前一個揹包,而是當前揹包。所以我們要考慮的當然是當前狀態。
#include <iostream>
#include<cstring>
using namespace std;
const int nmax=1000;
int v[nmax];//v[i]表示第i個物品的價值value
int w[nmax];//w[i]表示第i個物品的重量weight
int dp[nmax];//總價值
int n,m;//n表示物品數量,m表示揹包容量
int main(int argc, char** argv) {//一維陣列實現的完全揹包模板
while(cin>>n>>m){
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++){
cin>>w[i]>>v[i];
}
for(int i=0;i<n;i++){//遍歷n個物品
for(int j=0;j<=m;j++){//完全揹包容量 順序遍歷
if(j>=w[i]){
dp[j]=max(dp[j],(dp[j-w[i]]+v[i]));
}//第i個物體不選,dp[j]=dp[j];
//第i個物體若選 dp[j]=dp[j-w[i]]+v[i]
}
}
cout<<dp[m]<<endl;
}
return 0;
}
參考連結: