多重分组与二维背包
多重背包如何转化为 0-1 背包?
- 条件: 第
i件物品最多选count[i]件; - 做法: 在 0-1 背包内侧再枚举选择件数
k(0 <= k <= count[i]); - 方程:
dp[i][c] = max(dp[i-1][c], dp[i-1][c - k * wgt[i-1]] + k * val[i-1]); - 约束:
k * wgt[i-1] <= c才允许放入; - 复杂度: 时间
O(n * cap * maxCount);
const multiKnapsackDP = (wgt, val, count, cap) => {
const n = wgt.length;
const dp = Array.from({ length: n + 1 }, () => new Array(cap + 1).fill(0));
for (let i = 1; i <= n; i++) {
for (let c = 0; c <= cap; c++) {
for (let k = 0; k <= count[i - 1] && k * wgt[i - 1] <= c; k++) {
dp[i][c] = Math.max(dp[i][c], dp[i - 1][c - k * wgt[i - 1]] + k * val[i - 1]);
}
}
}
return dp[n][cap];
};
- 空间优化: 压缩为一维后按 0-1 背包倒序遍历, 见 0-1 背包;
分组背包如何保证每组只选一个?
- 条件: 物品分成
n组, 第i组有groupCount[i]个物品, 每组最多选一个; - 状态:
dp[i][c]为前i组在容量c下的最大价值; - 方程: 不选该组用
dp[i-1][c]; 选该组第j个用dp[i-1][c - wgt[i-1][j]] + val[i-1][j]; - 关键: 组内遍历
j时取最大值, 而不是像多重背包那样累加件数, 从而天然互斥;
const groupKnapsackDP = (wgt, val, cap) => {
const groups = wgt.length;
const dp = Array.from({ length: groups + 1 }, () => new Array(cap + 1).fill(0));
for (let i = 1; i <= groups; i++) {
for (let c = 0; c <= cap; c++) {
dp[i][c] = dp[i - 1][c];
for (let j = 0; j < wgt[i - 1].length; j++) {
if (wgt[i - 1][j] > c) continue;
dp[i][c] = Math.max(dp[i][c], dp[i - 1][c - wgt[i - 1][j]] + val[i - 1][j]);
}
}
}
return dp[groups][cap];
};
二维背包如何增加一个容量维度?
- 条件: 除重量
cap外还有容量vol上限, 每件物品同时占用wgt[i]与volume[i]; - 状态:
dp[i][c][v]为前i件物品在重量c、容量v下的最大价值; - 方程:
dp[i][c][v] = max(dp[i-1][c][v], dp[i-1][c - wgt[i-1]][v - vol[i-1]] + val[i-1]); - 初值: 任一维为 0 时价值为 0;
- 答案:
dp[n][cap][vol]; - 空间优化: 压缩为二维数组, 两个维度都倒序遍历;
混合背包怎么处理?
- 条件: 同一批物品里
count[i]为 0 表示无限件, 为 1 表示只选一次, 为其它正整数表示限量; - 做法: 逐物品判断类型, 分别套用完全背包或 0-1 背包或多重背包的循环写法;
- 注意: 完全背包正序遍历, 其余倒序遍历, 同一份一维数组可以复用;