動態規劃最經典並且在機試中重點考查的問題——揹包問題。揹包問題的變體繁多,這裡主要討論3種。
1.0-1揹包
0-1揹包問題描述的是,有\(n\)件物品,每件物品的重量為\(w[i]\),其價值為\(v[i]\),現在有容量為\(m\)的揹包,如何選擇物品使得裝入揹包物品的價值最大。
首先介紹求解這個問題的動態規劃方法,其時間複雜度為\(O(nm)\),空間複雜度也為\(O(nm)\)。
設定一個二維陣列\(dp[ ][ ]\),令\(dp[i][j]\)表示前\(i\)個物品裝進容量為\(j\)的揹包能獲得的最大價值。那麼\(dp[n][m]\)的值就是0-1揹包問題的解。
0-1揹包的遞推式如下:
點菜問題
//0-1揹包問題 點菜問題 2024-03-17
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXM];
int main() {
int c, n;//容量為c,物品共n件
while(scanf("%d%d", &c, &n) != EOF) {
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &weight[i], &value[i]);
}
for(int i = 0; i <= n ; ++i) {
dp[i][0] = 0;
}
for(int i = 0; i <= c; ++i) {
dp[0][i] = 0;
}
//考慮第i件物品放入容量為j的揹包
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= c; ++j) {
if(j < weight[i]) {
dp[i][j] = dp[i -1][j];
} else {
dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i]);
}
}
}
//查表
printf("%d\n",dp[n][c]);
}
return 0;
}
最佳化空間複雜度-dp設為一維陣列
//0-1揹包問題 點菜問題-最佳化空間複雜度 2024-03-17
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXM];
int main() {
int c, n;//容量為c,物品共n件
while(scanf("%d%d", &c, &n) != EOF) {
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &weight[i], &value[i]);
}
for(int j = 0; j <= c; ++j) {
dp[j] = 0;
}
//考慮第i件物品放入容量為j的揹包
for(int i = 1; i <= n; ++i) {
for(int j = c; j >= weight[i]; --j) {
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//查表
printf("%d\n",dp[c]);
}
return 0;
}
FYI:一般採用下標為1開始儲存weight和value,因為我們規定的是下標為i的話是前i件物品,那麼預設下標為0的時候是什麼物品都沒有,是一個空集,這樣可以方便我們更加好的進行處理。
2.完全揹包
完全揹包問題和0-1揹包問題很像,二者唯一的區別就在於完全揹包裡面,同一件物品有無窮多件。有\(n\)件物品,每件物品的重量為\(w[i]\),其價值為\(v[i]\),每種物品的數量均為無限個,現在有容量為\(m\)的揹包,如何選擇物品使得裝入揹包物品的價值最大?
同樣設定一個二維陣列\(dp[MAXN][MAXM]\),令\(dp[i][j]\)表示前i個物品裝進容量為j的揹包能夠獲得的最大價值。陣列\(dp[n][m]\)即為完全揹包問題的解。
和0-1揹包問題一樣,只考慮第i件物品時,可將情況分為是否放入第i件物品兩種:
HDU 4508_減肥記
//完全揹包 2024-03-17
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1e5 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXM];
int main() {
int n, m;
while(scanf("%d", &n) != EOF) {
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &value[i], &weight[i]);
}
scanf("%d", &m);
for(int i = 0; i <= n; ++i) {
dp[i][0] = 0;
}
for(int j = 0; j <= m; ++j) {
dp[0][j] = 0;
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
if(j < weight[i]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j - weight[i]] + value[i]);
}
}
}
printf("%d\n",dp[n][m]);
}
return 0;
}
最佳化空間複雜度,請看這張圖修改dp陣列和遞推式(也可以說是狀態轉移)。
總之,完全揹包問題的特點是每類物品可選的數量為無窮,其解法與0-1揹包問題整體保持一致,與其不同的僅為狀態更新時的遍歷順序。
3.多重揹包
每件物品既不是隻有1件,又不是無限多件,而是有限個。多重揹包問題:有\(n\)種物品,每種物品的重量為\(w[i]\),其價值為\(v[i]\),每種物品的數量為\(k[i]\),現在有個容量為\(m\)的揹包,如何選擇使得裝入揹包物品的價值最大。
求解多重揹包的方法就是直接轉化為0-1揹包問題,不過(1)簡單粗暴,把有多個同一物品的每一個都看成是相同重量、相同價值的不同物品;(2)是對同種物品進行有技巧地拆分和捆綁,得到不同的組合,使得可以任意想獲得幾件此個物品,都能用捆綁的組合加一加得到。
注意這個技巧性的拆分:就是類似二進位制法拆,最後不滿足2的冪次倍的單獨捆綁為一個新物品。
HDU 2191
/*
* 題目名稱:珍惜現在,感恩生活
* 題目來源:HDU 2191
* 題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=2191
* 程式碼作者:楊澤邦(爐灰)
*/
#include <iostream>
#include <cstdio>
#include <climits>
using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1e4 + 10;
int dp[MAXM];
int value[MAXN]; //物品價值
int weight[MAXN]; //物品重量
int amount[MAXN]; //物品數目
int newValue[20 * MAXN]; //拆分後物品價值
int newWeight[20 * MAXN]; //拆分後物品重量
int main() {
int caseNumber;
scanf("%d", &caseNumber);
while (caseNumber--) {
int n, m;
scanf("%d%d", &m, &n); //n件物品,m容量的揹包
int number = 0; //分解後物品數量
for (int i = 0; i < n; ++i) {
scanf("%d%d%d", &weight[i], &value[i], &amount[i]);
for (int j = 1; j <= amount[i]; j <<= 1) {
newValue[number] = j * value[i];
newWeight[number] = j * weight[i];
number++;
amount[i] -= j;
}
if (amount[i] > 0) {
newValue[number] = amount[i] * value[i];
newWeight[number] = amount[i] * weight[i];
number++;
}
}
for (int i = 0; i <= m; ++i) {
dp[i] = 0; //初始化
}
for (int i = 0; i < number; ++i) {
for (int j = m; j >= newWeight[i]; --j) {
dp[j] = max(dp[j], dp[j - newWeight[i]] + newValue[i]);
}
}
printf("%d\n", dp[m]);
}
return 0;
}
不知道咋,我程式碼AC不了,感覺邏輯沒問題,拆分再重新捆綁,記錄新物品的數量,眼睛快瞎了,先這樣吧。