形式陈述
动态规划把序列递推 理路 递推关系 Recurrence relation 用先前项规定序列当前项的关系。 推广为状态集 S 上的方程:每个 s ∈ S 的值只由其直接依赖 pred ( s ) 的值组合而成,并为无依赖状态给出基例。若依赖关系是有限无环图,就用拓扑排序 理路 拓扑排序 Topological sort 给有向无环图顶点排列线性次序,使每条边从前指向后。 确定填表次序;也可从目标状态递归展开,用记忆化 理路 记忆化 Memoization 缓存递归子问题结果以避免重复计算的自顶向下动态规划技术。 保存每个已解状态。更一般地,依赖可以由严格关系 ≺ 给出:若 t ∈ pred ( s ) 时总有 t ≺ s ,且 ≺ 是良基的,则可用良基递归确定每个状态值。要得到有限算法,还需目标可达的依赖图有限,每个状态的直接依赖可有效枚举,基例与组合运算能在有限时间完成。良基性本身允许无限多个直接依赖,不能替代这些计算条件。
优化问题常有
F ( s ) = opt a ∈ A ( s ) { c ( s , a ) + F ( T ( s , a ) ) } , 这里 A ( s ) 是状态 s 允许的选择,c ( s , a ) 是这一步的贡献,T ( s , a ) 是选择后所依赖的较小状态;先固定这些含义,才有依据决定用最小值还是最大值合并。计数、可达性、概率传播和字符串识别使用相应的加法、逻辑或等运算,并不需要“最优子结构”。若有 N 个可达状态,每个状态检查至多 T 个转移,每次转移、查表及一个状态的初始化和存储均为常数成本,则直接实现需 O ( N ( T + 1 ) ) 时间和 O ( N ) 存储。大整数计数不能忽略单次加法的位成本。
若状态依赖含环,单纯按表格顺序代入已无定义。一种严格的扩展是先给状态值域配备偏序,再把全部状态方程写成单调算子 F ,并明确规定要取的解,例如最小不动点 lfp ( F ) ;还需为所用迭代证明收敛到该不动点。任意环状递推方程不会因为“可以反复更新”就自动成为动态规划:解可能不存在、不唯一,或更新根本不收敛。
直觉
动态规划先问“未来究竟需要哪些过去信息”,再把这份充分摘要定义为状态。当两条路径得到同一状态时,它们的后续计算便可合并。状态太少会丢失影响未来的信息,太多则把整段历史原样带入。因此核心工作是证明状态充分、转移完备,并给出合法求值顺序。
图片加载失败 动态规划状态依赖与填表顺序
例子与边界
动态规划主动识别并合并相同子问题,以状态表或记忆化避免重复计算;回溯法 理路 回溯法 Backtracking 深度优先枚举部分解并在不可能完成时撤销选择的搜索范式。 沿选择树枚举候选,并靠可行性或界限剪枝撤销选择。两者可以组合,但“存在递归分支”并不足以构成动态规划。
Fibonacci 递归的调用树重复解同一下标,记忆化后只剩 O ( n ) 个状态。最长公共子序列用两个前缀长度 ( i , j ) 作状态,因而有 ( m + 1 ) ( n + 1 ) 个前缀状态。0–1 背包可定义 d p [ i , w ] 为前 i 件物品在容量 w 下的最大价值。设第 i 件重量为 a i 、价值为 v i ,当 w ≥ a i 时,转移为 d p [ i , w ] = max { d p [ i − 1 , w ] , d p [ i − 1 , w − a i ] + v i } ;第一项是不选,第二项是选一次,两者都只能读取上一行。
只考虑一件重量 2 、价值 3 的物品,容量为 4 ,初始一维表全零。若容量从小到大更新,先把 d p [ 2 ] 设为 3 ,再用这个刚写入的值计算 d p [ 4 ] = d p [ 2 ] + 3 = 6 ,相当于把物品选了两次。从大到小更新时,计算 d p [ 4 ] 读到的 d p [ 2 ] 仍是上一行的 0 ,结果为 3 ;之后才改 d p [ 2 ] 。逆序不是口诀,而是在原地存储中保留递推依赖的旧版本。
若还要输出选中的物品,仅存最优数值不够;可在二维表记录每个状态采用哪条转移,再从目标状态沿选择倒推。压缩空间后这些历史选择可能被覆盖,需要另存信息或重新计算,不能把数值计算的空间界直接当成完整解重建的空间界。
编辑距离网格 理路 编辑距离的网格动态规划 Levenshtein distance dynamic programming · Wagner–Fischer algorithm 把插入、删除与替换写成前缀网格的三类边,求出最小改写费用并重建逐条可执行的脚本。 把这一区别具体化:完整前缀表能回溯插删替换脚本,滚动行只保留费用。Hirschberg 方法 理路 Hirschberg 线性空间序列比对 Hirschberg algorithm · Linear-space sequence alignment 用前向与反向滚动行认证最优路径的中线穿越点,释放分数数组后递归重建完整对齐。 先用前后向费用认证中线切口,再释放数组、递归重建;因此要分别核算工作数组、递归栈和输出。
状态设为“当前价值”却不保留已用容量,通常无法决定背包的后续可行性;反过来,把所有已选集合当作状态虽然充分,却退化为指数枚举。“写出了递推式”只是起点;还必须证明状态确实包含后续所需的全部信息,转移覆盖了所有合法情形,且所选求值规则真能得到指定的解。
最少回文分解 理路 最少回文分解动态规划 Minimum palindromic factorization · Palindromic length · 最小回文切分 以已覆盖的前缀长度为状态,枚举全部非空回文末块,计算最少块数并重建切点,区分线性回文预处理与二次方转移成本。 给出一个完整的前缀状态实例:枚举所有回文末块,再由前驱表输出真实切点。aaba 否定先取最长回文前缀的贪心;线性时间求好回文半径后,候选切点总数仍可能是二次方,因而预处理变快不等于整个DP变快。
推论与应用
记忆化 理路 记忆化 Memoization 缓存递归子问题结果以避免重复计算的自顶向下动态规划技术。 以需求驱动方式解状态,表格法则按依赖顺序主动求值。动态规划可用于 DAG 理路 有向无环图 Directed acyclic graph · DAG 不含有向环的有向图。 最短路、序列比对、背包、矩阵链乘、区间问题、树分解、概率模型与有限时域控制。其中计数、概率与可达性例子也说明,动态规划的主体是状态方程的有效求值,而不是优化问题所特有的“最优子结构”。它与分治的分界是是否复用重叠状态,与贪心的分界则是是否保留多种中间可能后再比较。
高级 DP 仍应从状态 DAG 看待,而不是把新技巧当模板名。子集动态规划 理路 子集动态规划 Subset dynamic programming 以所有子集为状态执行精确指数动态规划,并准确计算转移枚举量。 把 2 n 个子集作为节点,树宽 DP 理路 树宽上的动态规划 treewidth dynamic programming · DP on tree decompositions 以最大独立集的完整逐袋计算,证明树分解边界状态、合并去重与回溯,并区分宽度、袋数和分解成本。 把 bag 边界状态沿树分解传播,Color Coding 理路 Color Coding color-coding · 颜色编码法 随机把顶点染成 k 色,将简单子图的顶点互异约束转成颜色子集动态规划。 用随机着色压缩“互异顶点”约束;三者分别以指数状态、结构参数和随机化换可解性。四俄罗斯与字级并行 理路 四俄罗斯方法与字级并行 Four Russians method · bit parallelism · broadword programming 把状态切成可查表微块,或在一个机器字内并行处理多位,从而省去对数因子。 则批量执行一组有限转移,只改善每条边的处理成本,不改变状态依赖正确性。
另一些加速减少的是候选转移。Monge 四点不等式 理路 Monge 数组与交叉交换不等式 Monge array · Monge matrix 用任意四个有序格子的交换不等式控制行极小值的位置,并把平方分段费用转化成可证明的候选单调性。 可证明最左切点单调,分治 DP 优化 理路 决策单调的分治 DP 优化 Divide-and-conquer DP optimization 在已证明最优切点单调的分层递推中,先求中间状态,再用它收紧左右候选区间,逐层安全省去转移。 再据此收紧搜索范围;若转移还能展开为直线查询,直线下包络 理路 单调直线下包络优化 Monotone convex hull trick · Monotone line container 把候选转移改写成直线,在斜率与查询点都单调时用双端队列维护下包络,并以交点顺序证明删线安全。 利用斜率与查询顺序维护候选。每一种省略都要对应自己的结构证明。
固定位费用LZ77解析 理路 固定位费用的LZ77最优解析 Fixed-cost LZ77 parsing · Gamma-cost LZ77 parsing · 固定位码制的最优回指解析 固定字面量与gamma长度距离码的逐token位费用,以距离价格带和长度价格区间压缩完整转移,求可重建且可解码的最小payload。 提供另一种完整转移压缩:同一距离价格档先求最长可复制前缀,再把相同长度费用的一段目的位置交给区间最小值。每个合法长度仍被某个区间覆盖,所省的是枚举工作而不是未经证明地丢弃选择;只有码价不随此前解析改变时,单独记当前位置才足以决定剩余问题。
参考资料
MIT 6.006,Lecture 16: Dynamic Programming Subproblems ,2020,课程讲义 :状态定义、递推关系、求值顺序与解重建。
Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein, Introduction to Algorithms , 4th ed., MIT Press, 2022,Part IV, Ch. 14 “Dynamic Programming”。
Jon Kleinberg and Éva Tardos, Algorithm Design , Pearson, 2005,Ch. 6, dynamic programming and optimal substructure。