背包DP(claude版)
纯AI文
背包 DP
一、背包问题的数学抽象
给定背包容量 \(W\),以及 \(n\) 件物品,第 \(i\) 件物品的重量为 \(w_i\)、价值为 \(v_i\)。我们要求一个选择方案,使得在容量约束下总价值最大。
形式化地,设 \(x_i\) 表示第 \(i\) 件物品的选取数量,则目标是:
不同背包模型,区别仅在于 \(x_i\) 的取值范围:
二、01 背包
2.1 状态定义
2.2 状态转移方程
对第 \(i\) 件物品,"不拿"或"拿"二选一:
边界条件:
💡 关键点:转移中"拿"的情况是 \(dp[\,i-1\,][\,j-w_i\,]\),下标为 \(i-1\)。这保证了第 \(i\) 件物品只被选一次——这是 01 背包的命门。
2.3 具体数据手工模拟
设 \(W = 10\),\(n = 4\),物品如下:
填表结果(行 \(i\),列 \(j\)):
验证几格:
最优方案:物品 \(1+2+4\),重量 \(2+3+5=10\),价值 \(3+4+6=13\)。✅
2.4 空间优化与逆序遍历
由于 \(dp[i][\cdot]\) 只依赖 \(dp[i-1][\cdot]\),可压缩为一维 \(dp[j]\):
遍历方向的数学解释:我们希望等号右边的 \(dp[j-w_i]\) 仍是"上一层 \(i-1\)"的值。
- 若 \(j\) 递减遍历(\(W \to w_i\)):更新 \(dp[j]\) 时,\(dp[j-w_i]\)(下标更小)尚未在本层更新 \(\Rightarrow\) 保留旧值 \(\Rightarrow\) 正确。
- 若 \(j\) 递增遍历:\(dp[j-w_i]\) 已被本层更新,物品被重复使用 \(\Rightarrow\) 错误(退化成完全背包)。
for (int i = 1; i <= n; i++)
for (int j = W; j >= w[i]; j--) // 逆序:保证每件物品至多选一次
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
时间复杂度: \(O(nW)\),空间复杂度: \(O(W)\)。
三、完全背包
3.1 状态转移方程
每件物品可无限取,故"拿"的情况从 \(dp[i][\cdot]\) 转移(允许继续拿同一件):
💡 注意下标:01 背包是 \(dp[\,\boxed{i-1}\,][j-w_i]\),完全背包是 \(dp[\,\boxed{i}\,][j-w_i]\)。一字之差,语义天壤之别。
3.2 一维形式与正序遍历
正序遍历时,\(dp[j-w_i]\) 已是本层更新后的值,恰好实现"重复选取"。
for (int i = 1; i <= n; i++)
for (int j = w[i]; j <= W; j++) // 正序:允许物品重复使用
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
与 01 背包唯一的区别就是循环方向:
复杂度: \(O(nW)\)。
四、多重背包
4.1 朴素转移方程
第 \(i\) 件物品至多取 \(c_i\) 件:
朴素实现把每种物品拆成 \(c_i\) 个独立的 01 物品,复杂度:
4.2 二进制优化
核心引理:任意整数 \(c\) 都可由 \(\{1, 2, 4, \dots, 2^{k-1}, \, r\}\) 这组数的子集和表示,其中
即用约 \(\lceil \log_2(c_i+1) \rceil\) 个"打包物品"代替 \(c_i\) 个物品,每个打包物品按 01 背包处理。优化后复杂度降为:
拆分示例:\(c = 13\) 拆为
子集组合可覆盖 \(\{0, 1, 2, \dots, 13\}\) 的全部取值。✅
for (int i = 1; i <= n; i++) {
int num = c[i];
for (int k = 1; num > 0; k <<= 1) { // k = 1,2,4,...
int amount = min(k, num); // 这一捆的件数
num -= amount;
int W_pack = amount * w[i], V_pack = amount * v[i];
for (int j = W; j >= W_pack; j--) // 当作 01 物品,逆序
dp[j] = max(dp[j], dp[j - W_pack] + V_pack);
}
}
五、分组背包
5.1 状态转移方程
物品分为若干组,设第 \(g\) 组的物品集合为 \(G_g\),每组至多选一件:
5.2 一维实现与循环顺序
关键:必须 \(j\) 逆序在外层、组内物品 \(t\) 在内层,保证每组对每个 \(j\) 至多贡献一件:
for (int g = 1; g <= n; g++) // 枚举组
for (int j = W; j >= 0; j--) // 逆序容量(外层)
for (int t : group[g]) // 枚举组内物品(内层)
if (j >= w[t])
dp[j] = max(dp[j], dp[j - w[t]] + v[t]);
复杂度:\(O\!\left(W \sum_g |G_g|\right)\)。
六、总览(公式汇总)
记忆口诀(数学版):
七、补充:恰好装满的初始化
若要求恰好装满容量 \(W\),仅需修改初始化:
含义:只有容量 \(0\) 是合法初始状态,其余视为"不可达"。最终若 \(dp[W] = -\infty\) 则无解。
八、巩固练习
按递进难度推荐:
- P1048 采药 —— 裸 01 背包,验证 \(dp[n][W]\)。
- P1616 疯狂的采药 —— 完全背包,体会 \(j \uparrow\)。
- P1776 宝物筛选 —— 多重背包,练二进制拆分。
- P1757 分组背包 —— 分组背包模板。

浙公网安备 33010602011771号