背包DP(claude版)

纯AI文

背包 DP


一、背包问题的数学抽象

给定背包容量 \(W\),以及 \(n\) 件物品,第 \(i\) 件物品的重量为 \(w_i\)、价值为 \(v_i\)。我们要求一个选择方案,使得在容量约束下总价值最大。

形式化地,设 \(x_i\) 表示第 \(i\) 件物品的选取数量,则目标是:

\[\max \sum_{i=1}^{n} v_i x_i \quad \text{s.t.} \quad \sum_{i=1}^{n} w_i x_i \le W \]

不同背包模型,区别仅在于 \(x_i\) 的取值范围:

\[\begin{cases} x_i \in \{0, 1\} & \text{01 背包} \\[4pt] x_i \in \{0, 1, 2, \dots\} & \text{完全背包} \\[4pt] x_i \in \{0, 1, \dots, c_i\} & \text{多重背包} \\[4pt] \text{每组至多一件} & \text{分组背包} \end{cases} \]


二、01 背包

2.1 状态定义

\[dp[i][j] = \text{考虑前 } i \text{ 件物品、容量为 } j \text{ 时的最大价值} \]

2.2 状态转移方程

对第 \(i\) 件物品,"不拿"或"拿"二选一:

\[dp[i][j] = \begin{cases} dp[i-1][j] & j < w_i \quad (\text{装不下,只能不拿}) \\[6pt] \max\bigl(\, dp[i-1][j],\; dp[i-1][j-w_i] + v_i \,\bigr) & j \ge w_i \end{cases} \]

边界条件:

\[dp[0][j] = 0, \quad \forall\, j \in [0, W] \]

💡 关键点:转移中"拿"的情况是 \(dp[\,i-1\,][\,j-w_i\,]\),下标为 \(i-1\)。这保证了第 \(i\) 件物品只被选一次——这是 01 背包的命门。

2.3 具体数据手工模拟

\(W = 10\)\(n = 4\),物品如下:

\[(w_1,v_1)=(2,3),\;(w_2,v_2)=(3,4),\;(w_3,v_3)=(4,5),\;(w_4,v_4)=(5,6) \]

填表结果(行 \(i\),列 \(j\)):

\[\begin{array}{c|ccccccccccc} i \backslash j & 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 1 & 0 & 0 & 3 & 3 & 3 & 3 & 3 & 3 & 3 & 3 & 3 \\ 2 & 0 & 0 & 3 & 4 & 4 & 7 & 7 & 7 & 7 & 7 & 7 \\ 3 & 0 & 0 & 3 & 4 & 5 & 7 & 8 & 9 & 9 & 12 & 12 \\ 4 & 0 & 0 & 3 & 4 & 5 & 7 & 8 & 9 & 10 & 12 & \boxed{13} \end{array} \]

验证几格:

\[dp[2][5] = \max\bigl(dp[1][5],\; dp[1][2]+4\bigr) = \max(3,\,3+4) = 7 \]

\[dp[3][9] = \max\bigl(dp[2][9],\; dp[2][5]+5\bigr) = \max(7,\,7+5) = 12 \]

\[dp[4][10] = \max\bigl(dp[3][10],\; dp[3][5]+6\bigr) = \max(12,\,7+6) = \boxed{13} \]

最优方案:物品 \(1+2+4\),重量 \(2+3+5=10\),价值 \(3+4+6=13\)。✅

2.4 空间优化与逆序遍历

由于 \(dp[i][\cdot]\) 只依赖 \(dp[i-1][\cdot]\),可压缩为一维 \(dp[j]\)

\[dp[j] \leftarrow \max\bigl(dp[j],\; dp[j-w_i] + v_i\bigr) \]

遍历方向的数学解释:我们希望等号右边的 \(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\) 错误(退化成完全背包)。

\[\boxed{\text{01 背包:} j \text{ 从 } W \text{ 递减到 } w_i} \]

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]\) 转移(允许继续拿同一件):

\[dp[i][j] = \max\bigl(\, dp[i-1][j],\; dp[i][j-w_i] + v_i \,\bigr), \quad j \ge w_i \]

💡 注意下标:01 背包是 \(dp[\,\boxed{i-1}\,][j-w_i]\),完全背包是 \(dp[\,\boxed{i}\,][j-w_i]\)。一字之差,语义天壤之别。

3.2 一维形式与正序遍历

\[dp[j] \leftarrow \max\bigl(dp[j],\; dp[j-w_i] + v_i\bigr), \quad j \text{ 从 } w_i \text{ 递增到 } W \]

正序遍历时,\(dp[j-w_i]\) 已是本层更新后的值,恰好实现"重复选取"。

\[\boxed{\text{完全背包:} j \text{ 从 } w_i \text{ 递增到 } W} \]

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 背包唯一的区别就是循环方向

\[\text{01 逆序} \;\longleftrightarrow\; \text{完全正序} \]

复杂度: \(O(nW)\)


四、多重背包

4.1 朴素转移方程

\(i\) 件物品至多取 \(c_i\) 件:

\[dp[i][j] = \max_{0 \le k \le c_i,\; k \cdot w_i \le j} \bigl(\, dp[i-1][\,j - k\,w_i\,] + k\,v_i \,\bigr) \]

朴素实现把每种物品拆成 \(c_i\) 个独立的 01 物品,复杂度:

\[O\!\left(W \sum_{i=1}^{n} c_i\right) \]

4.2 二进制优化

核心引理:任意整数 \(c\) 都可由 \(\{1, 2, 4, \dots, 2^{k-1}, \, r\}\) 这组数的子集和表示,其中

\[r = c - (2^k - 1) \ge 0, \quad k = \lfloor \log_2(c+1) \rfloor \]

即用约 \(\lceil \log_2(c_i+1) \rceil\) 个"打包物品"代替 \(c_i\) 个物品,每个打包物品按 01 背包处理。优化后复杂度降为:

\[O\!\left(W \sum_{i=1}^{n} \log_2 c_i\right) \]

拆分示例\(c = 13\) 拆为

\[13 = \underbrace{1 + 2 + 4}_{2^3 - 1 = 7} + \underbrace{6}_{r = 13 - 7} \]

子集组合可覆盖 \(\{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\)每组至多选一件

\[dp[g][j] = \max\left(\, dp[g-1][j],\;\; \max_{t \in G_g,\; w_t \le j}\bigl(dp[g-1][\,j - w_t\,] + v_t\bigr) \right) \]

5.2 一维实现与循环顺序

\[dp[j] \leftarrow \max_{t \in G_g,\; w_t \le j}\bigl(dp[j],\; dp[j-w_t] + v_t\bigr) \]

关键:必须 \(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)\)


六、总览(公式汇总)

\[\begin{array}{|c|c|c|c|} \hline \textbf{类型} & \textbf{转移核心} & \textbf{遍历} & \textbf{复杂度} \\ \hline \text{01} & dp[j]=\max(dp[j],\,dp[j-w_i]+v_i) & j \downarrow & O(nW) \\ \hline \text{完全} & dp[j]=\max(dp[j],\,dp[j-w_i]+v_i) & j \uparrow & O(nW) \\ \hline \text{多重} & \text{二进制拆分} \to \text{01} & j \downarrow & O\!\left(W\sum \log c_i\right) \\ \hline \text{分组} & \max_{t \in G_g}(dp[j-w_t]+v_t) & j \downarrow,\, t \text{ 内层} & O\!\left(W\sum |G_g|\right) \\ \hline \end{array} \]

记忆口诀(数学版):

\[\underbrace{j \downarrow}_{\text{防重复}} \;\;\text{vs}\;\; \underbrace{j \uparrow}_{\text{求重复}}, \qquad \text{多重} = \sum \log c_i \text{ 个 01}, \qquad \text{分组:} j \text{ 必在物品外层} \]


七、补充:恰好装满的初始化

若要求恰好装满容量 \(W\),仅需修改初始化:

\[dp[0] = 0, \qquad dp[j] = -\infty \;\; (1 \le j \le W) \]

含义:只有容量 \(0\) 是合法初始状态,其余视为"不可达"。最终若 \(dp[W] = -\infty\) 则无解。


八、巩固练习

按递进难度推荐:

  1. P1048 采药 —— 裸 01 背包,验证 \(dp[n][W]\)
  2. P1616 疯狂的采药 —— 完全背包,体会 \(j \uparrow\)
  3. P1776 宝物筛选 —— 多重背包,练二进制拆分。
  4. P1757 分组背包 —— 分组背包模板。

posted @ 2026-06-06 08:41  头像是美食  阅读(8)  评论(0)    收藏  举报