背包问题九讲 · 动态规划的思考艺术
一类被反复讲解近二十年的 dynamic programming 入门题——把若干带「费用 C」和「价值 W」的物品放入容量 V 的背包,求最大价值。九讲从最朴素的 01 背包(每件至多选一次)出发,逐步增加约束、变换问法:无限选、有上限、多类混合、双重费用、分组互斥、物品依赖……最后抽象成「物品即函数 h(v)」与「换聚合算子即换问法」两条主线。
每页都可调整容量 / 物品参数、单步执行,观察 DP 表逐格填出、读取的父节点与正在写入的格子高亮、代码逐行点亮,最后回溯出究竟选了哪几件落入背包。脉络一条线:状态 → 转移方程 → 循环方向 → 组合 / 求和 → 回溯。
基础三讲:状态、方程、循环方向
01 背包:每件至多选一次
全系列的地基。状态 F[i][v] =「前 i 件、容量 v 内的最大价值」,每件物品只有「选 / 不选」两个分支取 max。单步看二维表逐格填出、回溯把选中的物品落进背包;再看一维压缩为何要 v 逆序。
完全背包:每种可无限次选
和 01 背包只差一个循环方向:把一维的 v 从逆序改成顺序,读到的 F[v-C] 就已经含「本物品已选过」的结果,于是允许反复选。左右并排同步单步,看同一格在两种方向下读到的源值差在哪。
多重背包:每种限选 M 件
介于 01 与完全之间——每种有件数上限 M。核心技巧是二进制拆分:把 M 件拆成 1, 2, 4, … 若干个「打包件」,任意 0..M 件都能由它们的子集唯一组合而成,于是退化成 01 背包,复杂度从 O(VM) 降到 O(V·log M)。
进阶四讲:混搭、升维、分组、依赖
混合背包:三类物品同台
01、完全、多重三种物品一起出现,却不需要任何新算法。state 与 F[v] 的语义都不变——变的只是转移过程:主循环按物品 type 分发到 ZeroOnePack / CompletePack / MultiplePack。这正是「抽象与组合」的方法论价值。
二维费用:同时占两种资源
每件物品同时消耗两种费用(如重量 + 体积),容量也有两个上限 V、U。状态从 F[v] 升一维成 F[v][u],转移方程、边界、空间压缩的全套技术原样迁移——「升维即可」,体现状态设计的可扩展性。
分组背包:每组至多选一件
物品分成若干组,每组至多选一件。互斥既不靠加维也不靠分发,而靠三层循环的嵌套顺序:外层组 k、中层容量 v 逆序、内层组内物品 j。若将内外层写反便会同组多选——本页正确 / 错误两版并排单步,直观对比差异。
依赖背包:附件依附主件
物品间有依赖(附件只有在选了主件时才能选)。求解是两步化归:先对每个主件的附件集做 01 背包预处理(Stage A),把「主件 + 一组附件」打包成分组背包里的候选,再跑分组背包(Stage B)。一般情形即树形 DP。
收束两讲:统一抽象与问法变化
泛化物品:物品即函数 h(v)
把「物品」抽象成函数 h(v) =「分配费用 v 时能贡献的最大价值」。01 是单峰、完全是等距阶梯、多重是截断阶梯……前七讲全是它的特例。背包问题归结为对所有 h 做一种「求和」f(v) = max{h(k)+g(v−k)},在 f(V) 读答案。
问法变化:换聚合算子即换问法
同一组数据、同一状态骨架,可以提出「最大价值 / 最少件数 / 方案数 / 可行性」等诸多问题。关键在于:状态不变,只换聚合算子——max 换成 min / sum / OR,再配上对应的初值。一个 tab 切换四种问法,观察 F 数组随之变化。
动手体验 · 应用 demo
它真实跑在哪里
原版出处
- 《背包问题九讲》v2.0 · 崔添翼 (Tianyi Cui, dd_engi) github.com/tianyicui/pack 本系列是这份 2012 年中文 OI / ACM 经典 DP 教程的交互注解版;原文以 LaTeX 写成,CC BY-NC-SA 授权。九讲的脉络、递推方程与「物品即函数」「换聚合即换问法」两条主线都源自原作。
- 《背包问题九讲》v2.0 · 原版 PDF 本地 · v2.pdf 崔添翼原文的 LaTeX 排版 PDF(随本系列归档),九讲的完整定义、证明与代码均以此为准。
- 二进制拆分 / 背包基础 · 参考 PDF 本地 · binary_fund.pdf 多重背包二进制拆分等基础推导的补充材料(随本系列归档)。
-
Knapsack problem — Wikipedia
en.wikipedia.org
背包问题的标准定义、伪多项式时间复杂度
O(NV)、与 NP-hard(0/1 knapsack 的判定版)的关系。