背包问题
背包问题
dp[i][j] 表示使用前i个物品,当前体积为j时 能取得的最大价值
for (枚举单个/单组物品) {
for (枚举体积) {
for (枚举选择) { // 选/不选 单个/单组物品
if (满足条件) {
记录结果
}
}
}
}
恰好装满
| 初始化 | 含义 |
|---|---|
| dp[0][0] = 0 | 使用前0个物品,恰好装满体积j=0,能得到最大价值0 |
| dp[0][j > 0] = -inf | 使用前0个物品,永远无法装满体积j > 0 |
| dp[i > 0][0] = -inf | 使用前i > 0个物品,永远无法恰好装满j=0,体积一定会超过0 |
*为防止溢出,-inf取-9999999即可
01背包的一维表示
2D状态转移方程:
画图:dp[i][j]只依赖于上一行(i-1)的当前列j,和上一行(i-1)的j左侧某列j-w[i]
则压缩成1D时,需要从右向左逆序遍历列j,以确保左侧的j-w[i]保留为上一行的结果,而不是被本行i的结果覆盖:
完全背包
由于每件物品i可以被选择多次,因此某个dp[i][j]的值应该为选i不超过容量限制j的最大值:
选择 0 件物品 i 的最大价值 = dp[i-1][j - 0 * w[i]] + 0 * v[i]
选择 1 件物品 i 的最大价值 = dp[i-1][j - 1 * w[i]] + 1 * v[i]
选择 2 件物品 i 的最大价值 = dp[i-1][j - 2 * w[i]] + 2 * v[i]
...
选择 k 件物品 i 的最大价值 = dp[i-1][j - k * w[i]] + k * v[i], k * w[i] <= j
但这样会引入第三维k的遍历,使用斜率优化将其裂项相消:
画图:dp[i][j]只依赖于上一行(i-1)的当前列j,和本行(i)的j左侧某列j-w[i]
则压缩成1D时,需要从左向右正序遍历列j,以确保计算dp[i][j]时利用到了本行左侧已算出的j-w[i],而不是还未算出的默认值:
多重背包
视角1:即完全背包,每个物品i能取的次数, 限制为数组m[i]次
for(物品i顺)
for(体积j逆)
for (物品i得选择个数k = 0 -> m[i]) {
dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i])
}
视角2:即01背包,第i个物品重复了m[i]次,每个重复物品只能用一次
💡快速幂思想:不用平均拆成m[i]份,按照二进制拆成1,2,4,...log(m[i])份,每份只用一次,照样能表示出总体积。如果m[i]不是2的幂,拆剩下的单独作为一个物品
List<newGoods> goods = new ArrayList<>();
原价值v[],原体积w[],原可用次数m[]
for(int i = 0; i < n; ++i) {
for(int k = 1; k <= m[i]; k *= 2) {
m[i] -= k;
goods.add(新价值=v[i] * k, 新体积=w[i] * k);
}
if (m[i] > 0) goods.add(新价值=v[i] * m[i], 新体积=w[i] * m[i]);
}
// 01背包...
分组背包
有N件物品和一个容量为V的背包。第i件物品的费用是Ci,价值是Wi。这些物品被划分为K组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。组内枚举即可。
for(int i = 0; i < n; ++i) {
int K = 组内物品个数
for(int j = m; j >= v[i]; --j) {
for k 0 -> K
dp[j] = max(dp[j], dp[j - w[0~k]] + v[0~k])]);
}
}

浙公网安备 33010602011771号