Skip to content

04 动态规划

本章对应考试中的:P1 矩阵链乘2011期末,20 分)、P2 最长公共子序列 LCS(多版本,15~20 分)、P3 投资分配2011影印)、P4 生产-库存计划2012影印/2013)、P5 货车分配2020)、P6 货物分销merged)、P7 公路广告牌样题1/试卷3)。

动态规划(DP)是本课程分值最高、最容易拿满分的大题。原因是:它的作答格式是完全固定的,资源分配类题目更是"换个数字的同一道题"。本章目标——让你能默写模板、能把表格手工填出来。


核心考点

动态规划(dynamic programming,简称 DP):把一个大问题拆成一系列小问题(子问题),从最小的子问题开始、把每个子问题的答案算出来存进一张表,后面要用就查表,从而避免重复计算,最终拼出大问题的答案。

考试考两种 DP 大题:

  1. 资源分配型(P3 投资、P4 生产库存、P5 货车、P6 货物):把有限的资源(钱、产量、车、货)分给若干对象,求总收益最大或总成本最小。这四道题是同一个模板的换皮,是性价比最高的考点。
  2. 经典型(P1 矩阵链乘、P2 LCS、P7 广告牌):每道有自己特定的状态转移方程,但作答框架一样。

所有 DP 大题的得分点都是固定的五样:状态变量、决策变量、状态转移方程、边界条件、手工填表过程。把这五样写全写对就能拿大头分。


从零开始的前置知识

为什么需要"存表"——重复子问题

先看一个不存表会出大问题的例子。斐波那契数定义为 F(n)=F(n1)+F(n2),边界 F(1)=F(2)=1。如果直接照定义递归计算 F(5)

  • F(5) 要算 F(4)F(3)
  • F(4) 又要算 F(3)F(2)
  • F(3) 又要算 F(2)F(1)……

你会发现 F(3) 被重复计算了,F(2) 被算了更多次。规模一大,重复次数呈指数增长,总计算量达到 O(2n) 量级。

DP 的解决办法:开一张表 F[1..n],从 F[1]F[2] 开始往后逐个填F[i]=F[i1]+F[i2]。每个 F[i] 只算一次,总共 n 步,复杂度降到 O(n)。这就是判断题里说的"动态规划通过增加空间复杂性来降低时间复杂性"(用一张表的空间,换掉重复计算的时间)。

DP 和分治的区别

分治(详见 02_分治算法.md)也把大问题拆成子问题,但分治的子问题互不相干(例如归并排序左右两半独立),所以不需要存表。DP 的子问题会互相重叠(像上面 F(3) 被多处用到),所以必须存表。判断一道题用不用 DP,关键就看子问题会不会重复出现

什么是"最优子结构"

最优子结构(optimal substructure):大问题的最优解里,包含了它的子问题的最优解。只有具备这个性质,DP 才能用"子问题最优"拼出"大问题最优"。本章所有题都具备这个性质,作答时不必证明,知道这个词的意思即可。


核心理论与方法

动态规划通用四步模板(必背)

任何 DP 大题,按下面四步写,再加手工填表,就齐全了。

第 1 步:设状态变量

状态变量:描述"当前局面"所需要的那几个量。资源分配类题目,状态通常是两样合一:"现在轮到第几个对象"+"手上还剩多少资源"。

写法示例:"设阶段 k 表示正在考虑第 k 个对象(项目/月份/公司/零售店);状态 s 表示此刻还可分配的资源数量。"

第 2 步:设决策变量

决策变量:这一步你要做的选择。

写法示例:"设决策 u 表示分配给第 k 个对象的资源量,取值范围 0us。"

第 3 步:写状态转移方程

状态转移方程(state transition equation):用"子问题的最优值"表示"当前问题的最优值"的等式,是 DP 的核心。资源分配类的通用形式(以求最大收益为例):

fk(s)=max0us{gk(u)+fk+1(su)}

逐符号解释:

  • fk(s):从第 k 个对象开始往后、手上有 s 份资源时,能取得的最优总收益。这是我们要填进表里的值。
  • gk(u):给第 k 个对象分配 u 份资源带来的直接收益(直接查题目给的收益表)。
  • fk+1(su):把剩下的 su 份资源留给后面对象,能取得的最优收益(这是已经算好、存在表里的子问题答案)。
  • max0us:在所有可能的分配量 u 里,挑使括号内总和最大的那个。

如果题目是求最小成本,就把 max 换成 min,把 gk(u) 换成成本即可。

第 4 步:写边界条件

边界条件:递推的起点。

写法示例:"边界 fn+1(s)0(没有对象可分配时,收益为 0)。"

第 5 步:手工填表(采分关键)

从边界出发,逆着对象顺序(先算最后一个对象的表 fn,再 fn1,……最后 f1),每个表把每个状态 s 对应的 fk(s) 算出来,并记下取到最优时的决策 u。最后从 f1 开始正向回溯,读出每个对象实际分了多少。这一步必须把计算过程写出来,只写最终答案不给分。

一个最小的演示例子

为了把模板讲透,先做一个最简单的:有 3 万元投给 2 个项目,求最大利润。 收益表(投 u 万元的利润):

投资额 u0123
项目 1 收益 g1(u)0479
项目 2 收益 g2(u)05810

阶段 k{1,2}状态 s = 剩余资金;决策 u = 投给项目 k 的钱;转移 fk(s)=max0us{gk(u)+fk+1(su)};边界 f3(s)0

先算 f2(最后一个项目,f2(s)=max0us{g2(u)+0}=max0usg2(u),因为 g2 递增,取 u=s):

s0123
f2(s)05810
最优 u20123

再算 f1(3)(手上 3 万,要在项目 1 投 u、剩下给项目 2):

f1(3)=max{u=0:g1(0)+f2(3)=0+10=10u=1:g1(1)+f2(2)=4+8=12u=2:g1(2)+f2(1)=7+5=12u=3:g1(3)+f2(0)=9+0=9=12.

最大利润 12 万元,在 u1=1(项目 1 投 1 万)时取到,此时剩 2 万给项目 2(f2(2)u2=2 取到)。所以最优方案:项目 1 投 1 万、项目 2 投 2 万,利润 4+8=12

下面四道资源分配大题(P3~P6)就是把"对象个数"和"收益表"换一换,方法一模一样


答题模板

【状态变量】设阶段 k = 第 k 个对象;状态 s = 当前剩余资源。
【决策变量】设 u = 分配给第 k 个对象的资源量,0 ≤ u ≤ s。
【状态转移方程】f_k(s) = max/min_{0≤u≤s} { g_k(u) + f_{k+1}(s-u) }。
【边界条件】f_{n+1}(s) ≡ 0。
【手工求解】从 f_n 逆序逐表计算(写出每个 max/min 的每一项),到 f_1;
            再从 f_1 正向回溯,读出每个对象分到多少。
【结果】最优值 = ___,最优方案 = ___。
【复杂度】状态数 × 每状态决策数 = O(___)。

逐题精解

P1 矩阵链乘最优结合(2011期末,20 分)

原题:写出用动态规划求矩阵链乘积 Al+1××Ah 的递推公式、伪代码和时间复杂性,并手工计算 d=A3,4×A4,6×A6,1×A1,5 的最优策略与最小开销(Ax,y 表示 x×y 阶矩阵)。

前置:两个矩阵相乘的代价。 一个 x×y 的矩阵乘一个 y×z 的矩阵,结果是 x×z 矩阵,需要做 xyz 次数乘法。例如 A3×4×B4×63×4×6=72 次乘法。

为什么加括号顺序会影响总代价。 矩阵连乘满足结合律,结果一样,但乘法次数不同。以 A3,4×A4,6×A6,1 为例:

  • 先算前两个:(A3,4×A4,6)×A6,1,代价 346+361=72+18=90
  • 先算后两个:A3,4×(A4,6×A6,1),代价 461+341=24+12=36

同样的结果,代价相差一倍多。DP 的任务就是找出代价最小的加括号方式。

递推公式。m[l,h] 表示把第 l 到第 h 个矩阵连乘起来的最小乘法次数。维数记为数组 d=[d0,d1,,dn],第 i 个矩阵是 di1×di 阶。

m[l,h]={0l=h (只有一个矩阵,不用乘)minli<h{m[l,i]+m[i+1,h]+dl1didh}l<h

逐符号解释:m[l,i] 是左半段 AlAi 的最小代价,m[i+1,h] 是右半段 Ai+1Ah 的最小代价,dl1didh 是把左右两个结果(dl1×didi×dh)合并相乘的代价;i 是"最后一次乘法的分界点",在所有可能的分界点里取最小。

伪代码:

text
MatrixChain(d[0..n]):
    for l ← 1 to n: m[l][l] ← 0          // 单个矩阵代价为 0
    for len ← 2 to n:                    // 链长从 2 到 n
        for l ← 1 to n-len+1:
            h ← l+len-1
            m[l][h] ← ∞
            for i ← l to h-1:            // 枚举分界点
                cost ← m[l][i] + m[i+1][h] + d[l-1]*d[i]*d[h]
                if cost < m[l][h]:
                    m[l][h] ← cost
                    s[l][h] ← i          // 记录最优分界点,便于回溯加括号
    return m[1][n], s

时间复杂性:O(n2)m[l,h] 要算,每个要枚举 O(n) 个分界点,故 O(n3)

手工计算d=[3,4,6,1,5],即 A1=3×4, A2=4×6, A3=6×1, A4=1×5)。

链长 2:

m[1,2]=d0d1d2=346=72,m[2,3]=d1d2d3=461=24,m[3,4]=d2d3d4=615=30.

链长 3:

m[1,3]=min{i=1:m[1,1]+m[2,3]+d0d1d3=0+24+341=36i=2:m[1,2]+m[3,3]+d0d2d3=72+0+361=90=36 (i=1).m[2,4]=min{i=2:m[2,2]+m[3,4]+d1d2d4=0+30+465=150i=3:m[2,3]+m[4,4]+d1d3d4=24+0+415=44=44 (i=3).

链长 4:

m[1,4]=min{i=1:m[1,1]+m[2,4]+d0d1d4=0+44+345=104i=2:m[1,2]+m[3,4]+d0d2d4=72+30+365=192i=3:m[1,3]+m[4,4]+d0d3d4=36+0+315=51=51 (i=3).

把这些值填进表(行 l、列 h,只填上三角):

m[l,h]h=1h=2h=3h=4
l=10723651
l=202444
l=3030
l=40

回溯加括号m[1,4]i=3 取到 → 切成 (A1A2A3)(A4)m[1,3]i=1 取到 → 切成 (A1)(A2A3)。合起来:

((A3,4×(A4,6×A6,1))×A1,5),最小开销 =51.

易错点:① 合并代价是 dl1didh,三个下标别取错(左段行数、分界点列数、右段列数);② 必须从短链往长链算,因为长链要用到短链的结果。


P2 最长公共子序列(LCS,多版本)

原题:写出用动态规划求两序列最长公共子序列的递推公式、伪代码和时间复杂性,并手工计算给定序列的 LCS。

前置:子序列 vs 子串。 子序列(subsequence):从原序列里按原顺序挑出若干字符(可以不连续)。例如 aceabcde 的子序列。子串(substring):必须是连续的一段。例如 bcd 是子串,bd 不是子串但是子序列。LCS 求的是公共子序列(两个序列都包含的、可不连续的最长那个)。

递推公式。 设两序列 X=x1x2xmY=y1y2ync[i,j] 表示 Xi 个字符与 Yj 个字符的 LCS 长度:

c[i,j]={0i=0 或 j=0c[i1,j1]+1xi=yjmax{c[i1,j], c[i,j1]}xiyj

逐符号解释:当两个当前字符相同(xi=yj),LCS 在"去掉这两个字符"的基础上加 1;当不同,就看"去掉 xi"和"去掉 yj"两种情况里哪个 LCS 更长。

伪代码:

text
LCS(X[1..m], Y[1..n]):
    for i ← 0 to m: c[i][0] ← 0
    for j ← 0 to n: c[0][j] ← 0
    for i ← 1 to m:
        for j ← 1 to n:
            if X[i] == Y[j]: c[i][j] ← c[i-1][j-1] + 1
            else:            c[i][j] ← max(c[i-1][j], c[i][j-1])
    return c[m][n]

时间复杂性: 表有 m×n 个格子,每个 O(1),故 O(mn)

版本 A 手工计算(来源 样题2 试卷2,15 分):X=abcbccY=cacbac

按递推逐格填表(行是 X 的字符,列是 Y 的字符):

cacbac
0000000
a0011111
b0011222
c0112223
b0112333
c0112334
c0112334

右下角 c[6,6]=4,即 LCS 长度为 4

回溯求出具体 LCS:从右下角出发,相等就取该字符并往左上走,不等就往值大的方向走(上或左):

  • (6,6)c=c → 取 c,走到 (5,5)
  • (5,5)c≠a,上下相等取上 → (4,5)(4,5)b≠a → 往左 (4,4)
  • (4,4)b=b → 取 b,走到 (3,3)
  • (3,3)c=c → 取 c,走到 (2,2)
  • (2,2)b≠a → 往上 (1,2)(1,2)a=a → 取 a,走到 (0,1) 结束。

逆序拼起来得 LCS = acbc(长度 4)。

其它版本结果(方法完全相同,自行填表即可):

  • 版本 B样题1/试卷1/试卷3):X=xzyzzyxY=zxyyzxzLCS 长度 5
  • 版本 C试卷4,20 分,要求写出全过程):X=xzyzzyxY=zxyyzxzyLCS 长度 5,如 xyzzyzyzzy

易错点:① 第 0 行、第 0 列要先填 0;② 回溯时若上、左值相等,选哪个都行,可能得到不同但等长的 LCS。


P3 投资分配(2011影印,8 分)

原题:8 万元投给 3 个项目,各项目在不同投资额下的利润见下表,求总利润最大的投资计划(写出状态变量、决策变量、状态转移方程与递推关系式,及手工求解步骤与结果)。

投资额(万元)012345678
项目 1 g105154080909598100
项目 2 g20515406070737475
项目 3 g30426404550515253

【状态/决策/方程】 阶段 k = 项目编号(1,2,3);状态 s = 还可投资的金额;决策 u = 投给项目 k 的金额。转移方程

fk(s)=max0us{gk(u)+fk+1(su)},f4(s)0.

【手工求解】

第一张表 f3(s)(最后一个项目,直接取 g3 的最大,因 g3 递增即 u=s):

s012345678
f3(s)0426404550515253

第二张表 f2(s)=max0us{g2(u)+f3(su)}(只需算到 s=8)。以 f2(8) 为例展示算法:

f2(8)=max{0+53, 5+52, 15+51, 40+50, 60+45, 70+40, 73+26, 74+4, 75+0}=110.

(上面九项分别对应给项目 2 投 u=0,1,,8。最大 110 在 u=5。)逐个算出整张表:

s012345678
f2(s)052640607086100110
最优 u20100/345445

第三张表只需 f1(8)

f1(8)=max0u8{g1(u)+f2(8u)}=max{0+110,5+100,15+86,40+70,80+60,90+40,95+26,98+5,100+0}=140.

最大值 140 在 u1=4 取到。

【回溯】 u1=4(项目 1 投 4,收益 80)→ 剩 4 → f2(4)u2=4 取到(项目 2 投 4,收益 60)→ 剩 0 → 项目 3 投 0。

【结果】 最大总利润 = 140 万元,方案:项目 1 投 4 万、项目 2 投 4 万、项目 3 投 0 万(80+60+0=140)。


P4 生产-库存计划(2012影印/2013

原题:未来四个月需求量为 月1=2、月2=3、月3=2、月4=4。每月若生产,固定成本 3 千元;每生产 1 单位成本 1 千元;每月生产批量不超过 6 单位;每单位每月库存费 0.5 千元;第 1 月初与第 4 月末均无库存。求总成本最低的生产与库存安排。

【状态/决策/方程】 阶段 k = 月份(1~4);状态 sk = 第 k月初库存;决策 uk = 第 k 月生产量,0uk6

  • 库存递推:sk+1=sk+ukdkdk 是第 k 月需求),且必须 sk+ukdk(不能缺货)。
  • 单月成本:costk=3[uk>0]+1uk+0.5sk+1。其中 [uk>0] 表示"生产了就记 1、没生产记 0"(固定成本只在生产时发生);0.5sk+1 是月末库存的存储费。
  • 转移方程(求最小):fk(sk)=minuk{costk+fk+1(sk+1)}
  • 边界:s1=0,要求 s5=0,故 f5(0)=0

【一个关键观察,帮你检查答案】 因为月初、月末库存都为 0,总生产量恒等于总需求 =2+3+2+4=11,所以"按单位算的生产成本"恒为 11×1=11,与方案无关。于是真正要优化的只有:固定成本 3×(生产的月数) 加上 库存费 0.5×(各月末库存之和)

【手工求解(逆序填表)】 状态 sk 的取值范围有限(库存不会超过后续总需求)。逐表计算:

f4(s4)(第 4 月,必须把库存清零,故 u4=4s4):

s401234
u443210
f4(s4)76540

(例:s4=0u4=4,成本 3+4+0=7s4=4u4=0 不生产,成本 0。)

f3(s3)d3=2f3(s3)=minu3{3[u3>0]+u3+0.5s4+f4(s4)}s4=s3+u32):

s30123456
f3(s3)111076.565.52
最优 u36500000

(例:f3(0)u3=6s4=4:成本 3+6+0.54=11,加 f4(4)=0,共 11。)

f2(s2)d2=3s3=s2+u23):

s201234
f2(s2)1615141110.5
最优 u254300

(例:f2(3)u2=0s3=0:成本 0,加 f3(0)=11,共 11。)

f1(0)d1=2s1=0,必须生产 u12):

f1(0)=min2u16{3+u1+0.5s2+f2(s2)},s2=u12.
u123456
s2=u1201234
当月成本 3+u1+0.5s256.589.511
+f2(s2)1615141110.5
合计2121.52220.521.5

最小 20.5u1=5 取到。

【回溯】 u1=5s2=3f2(3)u2=0s3=0f3(0)u3=6s4=4f4(4)u4=0

【结果】 最低总成本 = 20.5 千元,生产策略:月 1 生产 5、月 2 生产 0、月 3 生产 6、月 4 生产 0(靠库存覆盖月 2、月 4 的需求)。验证:固定成本 3×2=6 + 生产成本 11 + 库存费 0.5×(3+0+4+0)=3.5,合计 20.5。✓


P5 货车分配(2020,5 分)

原题:5 台货车分给 A、B、C 三个子公司,各公司分到不同车数的年利润见下表,求总利润最大的分配。

车辆数ABC
0000
1255
2696
3101111
4111212
5131213

【方程】 阶段 = 子公司(A,B,C),状态 s = 剩余车辆,fk(s)=max0us{gk(u)+fk+1(su)}fD0

【关键表】 逆序算 fC(=各车数下 C 的利润,递增取 u=s)、fBfA(5)

s012345
fC(s)056111213
fB(s)0510141620
fA(5)=max{0+fB(5),2+fB(4),6+fB(3),10+fB(2),11+fB(1),13+fB(0)}=max{20,18,20,20,16,13}=20.

【结果】 最大总利润 = 20。存在多个并列最优解,例如:A=0、B=2、C=3(0+9+11=20);A=2、B=2、C=1(6+9+5=20);A=3、B=1、C=1(10+5+5=20)。任写一个即可。


P6 货物分销(merged,8 分)

原题:一车 6 箱货沿途卸到 4 个零售店,各店卸下不同箱数的利润见下表,求总利润最大的卸货方案。

箱数 \ 店1234
14234
26455
37676
47886
57986
671086

【方程】 阶段 = 零售店 k(1~4),状态 sk = 到第 k 店时剩余箱数(s1=6),决策 uk = 在第 k 店卸下的箱数,fk(sk)=max0uksk{vk[uk]+fk+1(skuk)}f50

【结果】 按逆序填表(f4f3f2f1(6))求得 最大总利润 = 17,存在多种并列最优卸货方案,例如 (1,1,3,1)(2,2,1,1)(2,1,2,1) 等(验证 (1,1,3,1)4+2+7+4=17)。


P7 公路广告牌(样题1/试卷3

原题:一条由西到东长 M 公里的公路,可设广告牌的地点为 x1,,xn(已知各点坐标),各点放牌收益 p1,,pn,要求任两块广告牌距离不小于 3 公里,求总收益最大的放置方案。

【思路】 先把地点按坐标从西到东排序。对每个地点 i,定义 prev(i) = 满足 xjxi3最大下标 j(即"在 i 西边、与 i 至少隔 3 公里"的最后一个可放点)。设 f(i) = 只考虑前 i 个地点时的最大总收益。每个地点只有"放"或"不放"两种选择:

f(i)=max{f(i1)第 i 点不放, pi+f(prev(i))第 i 点放},f(0)=0.

逐符号解释:不放第 i 点,收益就是前 i1 点的最优 f(i1);放第 i 点,收益是它的 pi 加上"必须隔开 3 公里之外"的 f(prev(i))(因为 prev(i) 之后到 i 之间的点都离 i 太近不能再放)。答案是 f(n)

【小例子】 设地点坐标 x=[0,2,4,6]、收益 p=[5,6,3,7]。则 prevprev(1)=0(前面没有点),prev(2)=0x1=023=1? 否,故 0),prev(3):要 xj43=1,只有 x1=0,故 prev(3)=1prev(4):要 xj63=3x1=0,x2=2 满足,最大下标 2,故 prev(4)=2

  • f(0)=0f(1)=max{0,5+f(0)}=5
  • f(2)=max{f(1)=5, 6+f(0)=6}=6
  • f(3)=max{f(2)=6, 3+f(1)=8}=8
  • f(4)=max{f(3)=8, 7+f(2)=13}=13

最大收益 13(放第 2 点和第 4 点:6+7=13,坐标 2 与 6 相距 4≥3,合法)。

【复杂度】 地点已排序时,用双指针求所有 prev(i)O(n),填表 O(n),总 O(n);若用二分查找求 prevO(nlogn);若还要先排序则排序 O(nlogn) 主导。


高频陷阱 / 易错点小结

  1. 四步模板不能漏:状态、决策、转移方程、边界,缺一个扣一块分;手算过程必须写,光写答案不给分。
  2. 求最小成本时把 max 换成 min(P4),别套错方向。
  3. 填表顺序:资源分配类逆着对象顺序算(先 fnf1);矩阵链乘从短链到长链算;LCS 从左上到右下。顺序错了会用到还没算出的值。
  4. P4 固定成本只在"生产量大于 0"时计入,不生产的月份不算这 3 千元。
  5. 多个最优解(P5、P6)很常见,写出任意一个并验证总和正确即可。
  6. 回溯方案别忘做:题目要"安排计划",只给最优值不给方案会扣分。

自测清单

  • [ ] 不看书默写"动态规划通用四步模板"(状态/决策/转移方程/边界)。
  • [ ] 说清 DP 与分治的区别(子问题是否重叠、要不要存表)。
  • [ ] 独立把 P3 投资分配的三张表填出来,得到 140 与方案。
  • [ ] 独立把 P4 生产库存做出来,得到 20.5 与生产策略 (5,0,6,0)
  • [ ] 写出矩阵链乘的递推式,并把 d=[3,4,6,1,5] 算到最优 51。
  • [ ] 写出 LCS 递推式,把版本 A 的表填出来并回溯出 acbc
  • [ ] 写出广告牌的递推式并解释 prev(i) 的含义。