01揹包、完全揹包、多重揹包詳解

qq_22228095發表於2020-10-08

目錄

一、01揹包問題

二、完全揹包問題

三、多重揹包


一、01揹包問題

問題:

有n件物品和一個容量是m的揹包。第i件物品的體積是vi,價值是wi。
求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。輸出最大價值。

解答:

問題中每個物品只有選與不選兩種狀態。

動態規劃的狀態轉移方程:dp[i][j] = max(dp[i-1][j], dp[i][j-vi]+wi) , d[i][j]表示只看前i個物品且總體積是j時,最大的價值。

//v是物品的體積,w是價值,m是揹包容量
int package(vector<int> &v, vector<int> &w, int m) {
	int n = v.size();
	vector<vector<int>> dp(n+1, vector<int>(m+1,0));
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			dp[i][j] = dp[i - 1][j];
			if(j >= v[i-1])
				dp[i][j] = max(dp[i][j], dp[i-1][j - v[i-1]] + w[i-1]);
		}
	}
	return dp[n][m];
}

程式碼分析:

首先,最外層遍歷物品(1~n)。內層遍歷揹包容量(1~m)。在實現dp[i][j] = max(dp[i-1][j], dp[i][j-vi]+wi)狀態轉移時,在計算dp[i][j]時,需要使用dp[i-1][j]的內容。觀察程式碼可以發現每次的狀態轉移只與上一次的結果有關,會使dp[i-1][j] 與dp[i-1][j-vi]。

我們反向使用上一次的結果可以簡化輔助空間得到下面的狀態轉移:dp[j] = max(dp[j], dp[j-vi]+wi)

int package(vector<int> &v, vector<int> &w, int m) {
	int n = v.size();
	vector<int> dp(m + 1, 0);
	for (int i = 0; i < n; i++) {
		for (int j = m; j >= v[i]; j--) {
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	return dp[m];
}

 

二、完全揹包問題

問題:

有n種物品,每種物品可以選無數次和0次,揹包的容量是m。第i件物品的體積是vi,價值是wi。
求:將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。輸出最大價值。

分析:

問題中每個物品擁有選擇多個和不選的狀態。

動態規劃的狀態轉移方程與01揹包相同dp[j] = max(dp[j], dp[j-vi]+wi) , d[j]表示總體積是j時,最大的價值。

int package(vector<int> &v, vector<int> &w, int m) {
	int n = v.size();
	vector<int> dp(m + 1, 0);
	for (int i = 0; i < n; i++) {
		for (int j = v[i]; j <= m; j++) {
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	return dp[m];
}

與01揹包問題的程式碼略微有點不同,最外層同樣是遍歷物品(1~n)。內層遍歷從v[i]開始到m

三、多重揹包

問題:

有n種物品和一個容量是m的揹包。第i種物品的體積是vi,價值是wi,數量是si
求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。輸出最大價值。

可以把多重揹包看作是01揹包處理,使用與01揹包相同的方法。

int package(vector<int> &v, vector<int> &w, vector<int> &s, int m) {
	int n = v.size();
	vector<int> dp(m + 1, 0);
	for (int i = 0; i < n; i++) {
		for (int j = m; j >= v[i]; j--) {
			for (int k = 1; k <= s[i] && k*v[i] <= j; k++) {
				dp[j] = max(dp[j], dp[j - k*v[i]] + k*w[i]);
			}		
		}
	}
	return dp[m];
}

程式碼分析:

與01揹包同樣的方法 ,只是把每一個物品的不同數量si當做是有si種物品進行處理,多了一層對同一物品的迴圈。

時間複雜度O(n3)

二進位制優化的方法

學過linux作業系統的同學應該知道,修改linux檔案的許可權的數字法。

 如何使用最少的數的組合表示一個數的全域。比如:用一些數字或它們的組合來表示(1~7)所有的數。

0 0 0
4 2 1

在二進位制中,7 = 4+2+1,6=4+2,5=4+1,4=4,3=2+1,2=2,1=1

在上一個方法中,我們需要遍歷同一物品的所有的個數。複雜度很高,使用二進位制方法可以,有效降低時間複雜度。

比如有一種物品i有si件,如果si是7時。我們可以把這些物品拆分成4件,2件,1件。將4件物品的體積與價值分別相加當作一件新的物品加入物品集合,接下來2件,1件同理。我們就得到了一個每種物品只有1件的物品集合。可以用01揹包方法來解決。

int package_bit(vector<int> &v, vector<int> &w, vector<int> s, int m) {
	int n = v.size();
	vector<int> vr, wr;
	for (int i = 0; i < n; i++) {
		for (int k = 1; k <= s[i]; k *= 2) {
			s[i] -= k;
			vr.emplace_back(k*v[i]);
			wr.emplace_back(k*w[i]);
		}
		if (s[i] > 0) {
			vr.emplace_back(s[i] * v[i]);
			wr.emplace_back(s[i] * w[i]);
		}
	}
	vector<int> dp(m + 1, 0);
	for (int i = 0; i < vr.size(); i++) {
		for (int j = m; j >= vr[i]; j--) {
			dp[j] = max(dp[j], dp[j - vr[i]] +wr[i]);
		}
	}
	return dp[m];
}

 

相關文章