1. 程式人生 > >【演算法總結】01揹包與完全揹包 模板整理

【演算法總結】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;
}

參考連結: