動態規劃_揹包問題
阿新 • • 發佈:2020-07-11
揹包問題:
- 問題描述有\(n\)件物品, 每件物品的體積為\(V_i\),價值為\(W_i\), 有一個體積為\(V\)的揹包, 求總體積不大於\(V\)的所有物品總價值最大是多少
01揹包問題: 每件物品只能用一次
狀態表示: \(dp[i][j]\)
- 集合:所有選法
- 條件:僅從前\(i\)個物品中選擇,而且使得總體積不超過\(j\)
- 屬性:\(dp[i][j]\), 最大價值
狀態計算: 集合的劃分
樸素做法:二維
void solve() { for(int i = 1; i <= N; i++) { for(int j = 0; j <= V; j++) { dp[i][j] = dp[i - 1][j]; if(v[i] <= j) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]); } } printf("%d\n", dp[N][V]); }
優化版本:等價變形,每一層由於上一層有關
void solve() {
for(int i = 1; i <= N; i++)
for(int j = V; j >= v[i]; j--)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
printf("%d\n", dp[V]);
}
完全揹包:每件物品無使用次數的限制
樸素做法
void solve() { for(int i = 1; i <= n; i++) // 列舉所有用到物品 for(int j = 0; j <= V; j++) // 列舉所有體積 for(int k = 0; k * v[i] <= j; k++) // 列舉每件物品用到的次數 dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + w[i] * k); cout << dp[n][V]; }
優化-1
優化思路:
\(dp(i, j) = dp(i - 1, j - v_i \times k) + w_i \times k\)
\(dp(i, j)\)展開式:
\(dp(i, j) = max(dp(i - 1, j), dp(i - 1, j - v_i) + w_i, dp(i - 1,j - 2 \times v_i + 2 \times w_i,...)\)
\(dp(i,j - v_i) = max(dp(i - 1,j - v), dp(i - 1,j - 2 \times v_i + w_i, ...)\)
由以上兩式可得:
\(dp(i, j) = max(dp(i - 1, j), dp(i, j - v_i) + w_i)\)
- \(dp(i, j) = dp(i - 1, j)\) 表示第i個物品不選
- \(dp(i, j) = dp(i, j - v_i) + w_i)\): 表示第i個物品選若干個
void solve() {
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= V; j++) {
dp[i][j] = dp[i - 1][j];
if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
}
}
cout << dp[n][V];
}
優化-2: 變成一維
void solve() {
for(int i = 1; i <= n; i++) {
for(int j = v[i]; j <= V; j++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
cout << dp[V];
}
完全揹包與01揹包的區別:狀態方程的比較
- 01揹包:\(dp(i,j) = max(dp(i - 1, j), dp(i - 1, j - v_i) + w_i)\)
- 完全揹包: \(dp(i, j) = max(dp(i - 1, j), dp(i, j - v_i) + w_i)\)
多重揹包:每個物品的數量有限:僅有s[i]
個
樸素暴力做法: \(O(nms)\)
void solve() {
for(int i = 1; i <= n; i++)
for(int j = 0; j <= V; j++)
for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + w[i] * k);
cout << dp[n][V];
}
優化版本1:二進位制拆分優化: \(O(nmlogs)\)
- 例如: 體積為\(v\)的物品有\(s\)個,將這些物品按照2的冪次方個物品打包成新物品,可將其轉化成01揹包問題。
- 對每個物品的個數進行優化
- 假設有1023個物品,用多少個數可以表示從0到1023之間任意一個數?
- 將1023按照二進位制表示拆分成十個數(\(log1023 < 10\)),每個數表示其二進位制表示中的一位
void solve() {
int cnt = 0;
for(int i = 0; i < n; i++) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
int k = 1;
while(k <= c) {
cnt++;
v[cnt] = k * a;
w[cnt] = k * b;
c -= k;
k *= 2;
}
if(c) { // 2^k + c == v
cnt++;
v[cnt] = a * c;
w[cnt] = b * c;
}
}
for(int i = 1; i <= cnt; i++)
for(int j = m; j >= v[i]; j--)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
printf("%d\n", dp[m]);
}
優化版本2:滑動視窗:\(O()\)
分組揹包
void solve() {
for(int i = 1; i <= n; i++) {
scanf("%d", &s[i]);
for(int j = 0; j < s[i]; j++)
scanf("%d%d", &v[i][j], &w[i][j]);
}
for(int i = 1; i <= n; i++)
for(int j = m; j >= 0; j--)
for(int k = 0; k < s[i]; k++)
if(v[i][k] <= j)
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
printf("%d\n", dp[m]);
}
1013. 機器分配
- 每個公司當成一個物品組
- 可以選擇用\(dfs\)
- 可抽象成組合揹包問題
487. 金明的預算方案
二維費用的揹包問題+01揹包
- 狀態表示:\(dp[i, j, k]\),從前\(i\)個物品中選,總體積不超過\(j\)、總重量不超過\(k\)的總價值最大值
- 狀態計算:
1.如果不選擇第\(i\)個物品,\(dp[i][j][k] = dp[i - 1][j][k]\)
2.如果選擇第i個物品,\(dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - v[i]][k - w[i]] + b[i])\) - 解釋:當選擇了第\(i\)個物品,其總體積為\(j\),總重量為\(k\), 因此,去掉第\(i\)個物品,其總價值為\(dp[i - 1][j - v[i]][k - w[i]]\),再加上第\(i\)個物品的價值即為選擇第\(i\)個物品之後的總價值
1020. 潛水員
- 題目中的要求氧氣和氮氣體積至少為多少,求所需要的氣缸重量最小值
- 與常見揹包問題略有不同,通常的揹包問題要求體積不超過某一值
- 狀態表示:氧氣體積至少為\(j\),氮氣體積至少為\(k\),氣缸重量的最小值
- 初始化:\(dp[0][0] = 0\),其他狀態表示為正無窮
int x, y; scanf("%d%d", &x, &y);
int n; scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d%d%d", &o2[i], &n2[i], &v[i]);
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
for(int i = 1; i <= n; i++) {
for(int j = x; j >= 0; j--) {
for(int k = y; k >= 0; k--) {
// 當j - o2[i] < 0 時,表示氧氣體積至少為j - o2[i],此時該狀態是合法的,但該狀態數為0,氮氣同理
dp[j][k] = min(dp[j][k], dp[max(0, j - o2[i])][max(0, k - n2[i])] + v[i]);
}
}
}
printf("%d", dp[x][y]);
揹包求具體方案
- 題目要求輸出字典序最小的方案,因此需要逆序求\(dp陣列\),正序求方案