01背包是一種非常經典的動態規划問題,這里對01背包問題進行詳細解讀。
01背包問題題目描述
有 件物品和一個容量為 的背包。第 件物品的體積是 ,價值是 ,求將哪些物品裝入背包可使價值總和最大。
01背包問題解析
對於所有的動態規划問題,第一步都是確定狀態。我們定義狀態
是表示目前正在枚舉第
個物品,目前已取的總體積為
,最大價值為
。
第二步自然是找狀態轉移方程。
首先我們注意一點:物品只有取和不取兩種選擇,這是符合我們日常生活的。狀態轉移方程就需要從這里為突破口來思考:
(1):假如我們不取這個物品,那么
肯定是能從上一個物品,同樣體積轉移過來的,所以
。
(2):假如我們取這個物品,那么
是從什么情況轉移呢?思考一下,首先可以得出,
肯定可以從上一個物品轉移過來,那可以從什么體積轉移呢?我們注意到,對於
,它的當前取到的體積為
,由於我們取了這個物品,所以上一個物品的體積為
。所以我們可以得出,
可以從
轉移過來,由於我們取了這個物品,所以還要加上這個物品的價值
。
所以我們可以得出01背包的狀態轉移方程(很重要,盡量背下來!!)
時,
時,
。
上代碼:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[21][1010];
int w[21], c[21];
int main() {
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++) {
cin >> w[i] >> c[i];
}
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= V; j++) {
if (j >= c[i]) {
dp[i][j] = max(dp[i - 1][j - c[i]] + w[i], dp[i - 1][j]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
cout << dp[N][V] << endl;
return 0;
}
01背包滾動數組空間優化
我們分析一下空間復雜度:
,顯然較大,我們要優化一下,用什么優化呢?
我們可以用滾動數組!
觀察轉移方程,易得
只從
和
轉移,所以可以用一個
代替
,用
代替
。
這樣只需要定義 ,大大節省了空間。
上代碼:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[2][1010];
int w[21], c[21];
int main() {
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++) {
cin >> w[i] >> c[i];
}
int flag = 1;
for (int i = 1; i <= N; i++) {
for(int j = 0;j <= V; j++) {
if(j >= c[i]) {
dp[flag][j] = max(dp[1 - flag][j - c[i]] + w[i], dp[1 - flag][j]);
} else {
dp[flag][j] = dp[1 - flag][j];
}
}
flag = 1 - flag;
}
cout << dp[1 - flag][V] << endl;
return 0;
}
01背包空間優化
這里給大家介紹真正的空間優化。
如果我們將
數組只用來表示體積,那么我們可以讓內層循環的
從
到
枚舉,那么當前狀態轉移方程的
和
由於我們沒有更新,所以仍然是計算上一輪
個物品的,就是二維狀態下的
和
。所以現在我們的轉移方程是:
上代碼:
#include <iostream>
#include <cstring>
using namespace std;
int dp[1010];
int w[21], c[21];
int main() {
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++) {
cin >> w[i] >> c[i];
}
for (int i = 1; i <= N; i++) {
for (int j = V; j >= c[i]; j--) {
dp[j] = max(dp[j - c[i]] + w[i], dp[j]);
}
}
cout << dp[V] << endl;
return 0;
}