椰程信奥·互动课件 专题17·动态规划2:区间DP与线性DP进阶
10:00 1 / 14
🧭 椰程信奥 · CSP复赛专题系列

专题17 · 动态规划2

上一讲的状态是"前 i 个",这一讲换成"区间 i ~ j"。
区间 DP 的填表顺序不再是从左到右,而是按区间长度从小到大、斜着填—— 这是本讲唯一也是最重要的一个新动作。
四道题:最长回文子串、二叉排序树计数(卡特兰数)、回文词、LCS 应用。

区间DP:枚长度 → 枚起点 → 枚分割点 线性DP进阶:LCS 应用与回文词 卡特兰数 · 最长回文子串
引入

状态从"前缀"变成"一段"

线性 DP

状态:dp[i] 或 dp[i][j] = 前缀(前 i 个 / 前 i 与前 j)。
填表顺序:从左到右,因为 dp[i] 只依赖更小的 i。

区间 DP

状态:dp[i][j] = 区间 i ~ j 的最优值。
填表顺序:按长度从小到大,因为 dp[i][j] 依赖更短的区间。

区间 DP 的三层循环(固定套路)

for (int len = 2; len <= n; len++)            // ① 先枚区间长度(从短到长)
    for (int i = 1; i + len - 1 <= n; i++){      // ② 再枚起点
        int j = i + len - 1;                      //    终点就定了
        for (int k = i; k < j; k++)               // ③ 最后枚分割点
            dp[i][j] = max/min/求和(dp[i][j], dp[i][k] 与 dp[k+1][j] 的组合);
    }
⚠️ 第一层必须是长度。 很多人习惯写成 for i ... for j ...,那样算 dp[i][j] 时 里面的小区间还没算出来,结果是 0 或随机值。
3D🎲 区间 DP = 按长度分层的金字塔🖱 拖拽旋转 · 双击复位
为什么用 3D:区间 DP 的填表顺序(按长度从小到大)是最大难点。做成分层金字塔后,顺序就是「自下而上」,不可能填反。
动画①

同一张表,两种顺序,一个对一个错

格子里的箭头表示"这一格依赖谁"。绿色 = 依赖已就绪,红色 = 依赖还没算。
例题1

两头相等 + 里面是回文

状态与转移

dp[i][j] = 子串 s[i..j] 是否是回文(0/1)。
dp[i][j] = (s[i] == s[j]) && (len == 2 || dp[i+1][j-1])
初值:dp[i][i] = 1(单个字符必是回文)。
答案:所有为 1 的格子里长度最大的那个。

为什么依赖"里面一层"

回文去掉两头仍是回文。所以 s[i..j] 是回文 ⟺ 两头字符相等 且 s[i+1..j-1] 是回文。
而 s[i+1..j-1] 比它短 2,按长度枚举时已经算好了。

⚠️ 子串 vs 子序列: 子串必须连续。"abba" 的最长回文子串是 4, 而它的最长回文子序列也是 4;但 "abca" 的子串答案是 1,子序列答案是 3(aba)。 用 LCS 求出来的是子序列,别混用。
动画②

一圈一圈长大:长度 1 → 2 → 3 …

点「填一层」:先看长度 1(对角线),再看长度 2、3……
例题2

乘法原理 + 加法原理 = 区间DP

为什么可以拆成左右子树

二叉排序树的性质:根左边的都比根小,右边的都比根大。
所以当根是 k 时,左子树只能用 1 ~ k−1,右子树只能用 k+1 ~ n —— 两边互不相干。

两个原理怎么用

乘法原理:左右各自独立 → 方案数相乘 dp[i][k−1] × dp[k+1][j]。
加法原理:根可以是 i ~ j 中任意一个,各类互斥 → 求和。

for (int i = 1; i <= n + 1; i++) for (int j = 0; j <= n; j++)
    if (i > j) dp[i][j] = 1;              // ★ 空树 = 1 种,最容易漏

for (int len = 1; len <= n; len++)
  for (int i = 1; i + len - 1 <= n; i++){
      int j = i + len - 1; dp[i][j] = 0;
      for (int k = i; k <= j; k++)        // 枚举谁当根
          dp[i][j] += dp[i][k-1] * dp[k+1][j];
  }
答案序列:1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796 —— 这就是卡特兰数。n 个节点出栈序列、n 对括号合法匹配,答案都一样。
动画③

看每个根怎么把区间切成两半

n =
点「算一格」:每个格子会依次尝试让区间里的每个节点当根。
线性DP

最少插几个字符变成回文

转化思路(很妙)

把原串 反转让它变成 B,求 LCS(原串, B)。
LCS 就是"正读反读都能对上"的那部分,不需要动;
剩下对不上的字符,每个都要补一个。
答案 = 原串长度 − LCS 长度。

举例

原串 abcda,反转 adcba。
LCS = aba(长度 3)或 ada(长度 3)。
答案 = 5 − 3 = 2(补上 d 和 c 的对称字符)。

⚠️ 注意这是"回文子序列"的思路,不是上两页的"回文子串"。 补齐字符允许插在任意位置 → 对应子序列;求最长回文子串必须用区间 DP。 题意里"插入任意位置"→ LCS;"连续"→ 区间DP。
动画④

原串 vs 反转串,填一张表

原串 = abcda,反转 = adcba。填完看右下角。
代码

换一道题,只改第三层

最长回文子串

for len = 2..n
  for i, j = i+len-1
    dp[i][j] = (s[i]==s[j]
        && (len==2 || dp[i+1][j-1]));

依赖的是内层区间,不枚分割点。

二叉排序树计数

for len = 1..n
  for i, j = i+len-1
    for k = i..j
      dp[i][j] += dp[i][k-1]*dp[k+1][j];

要枚分割点 k,把区间切成两半。

以后遇到区间 DP,问自己三个问题

① 这格依赖的是"里面一层"还是"切成两半"?
② 空区间 / 长度为 1 的初值是多少?(区间题 80% 的 WA 出在这里)
③ 答案是 dp[1][n] 还是所有区间的最大值?

辨析

本讲四个高频失分点

① 第一层循环写成 i

❌ for i … for j … → 算 dp[i][j] 时内层区间还没算。
✅ 第一层必须是 len。

② 空区间初值漏掉

❌ BST 计数里不设 dp[i][j]=1 (i>j) → 全部为 0。
✅ 空树算 一种方案。

③ 子串 / 子序列混淆

❌ 用 LCS 求最长回文子串。
✅ 连续 → 区间DP;可插入 → LCS。

④ 爆 int

❌ 卡特兰数增长极快,n=20 就超 21 亿。
✅ long long。

知识树

动态规划2 ─┬─ 线性DP:LIS / LCS / 回文词(LCS 应用)
      └─ 区间DP:最长回文子串(内层依赖)/ 二叉排序树计数(枚分割点)→ 卡特兰数

拿分

一道题 100 分是分档给的——不会正解也要先拿保底

复赛一道题的 100 分由十几到二十几个测试点组成, 前面几档专门为「没想出正解的人」准备。四道题各拿 30~40 分, 加起来往往就是一等奖和二等奖的分界。

第 1 档 特判 5~10 分 约 2 分钟 第 2 档 暴力 20~30 分 约 8 分钟 第 3 档 特殊性质 40~60 分 约 15 分钟 第 4 档 正解 100 分 约 25 分钟

① 特判档:先别想算法,看数据范围表

题目给的数据范围表就是出题人给你的送分清单。看到「n = 1」「只有一组数据」这类一行, 就先写个 if 直接输出答案。2 分钟换 5~10 分,全场最划算。

② 暴力档:按题意最直白地写一遍

多重循环、DFS 全枚举、朴素 O(n³)……不要优化,只要保证最小那档全对。 它既是保底分,又是后面对拍的标尺——没有它你无法证明正解是对的。

③ 特殊性质档:题目里那句「若……」

常见的送分性质:数据已经有序、所有值完全相同、规模小到可以 O(n²)、 只出现一种类型。题目写出来就是让你拿的,专门写一份即可。

④ 正解档:思路定了,就赢了八成

考场上的时间几乎都花在想思路上,不是写代码。写完先跑样例, 再用第 ② 档的暴力版造小数据对拍——这是唯一能证明你思路对的办法。

考场纪律:每档设时间上限,到点还没调通就 立刻提交当前版本,保住已有分数,再往上冲。最常见的翻车是"正解写了 50 分钟没过,连暴力分都没交上去"。
拿分

本讲 2 道题,各自的四档怎么走

最长回文子串 · PALIN

第1档 · 2 分钟
长度 1 → 1;所有字符相同 → 答案就是长度本身

第2档 · 8 分钟
枚举所有子串 O(n³) 判回文,n ≤ 300 稳过

第3档 · 10 分钟
中心扩展,先只做奇数长度(或只做偶数长度)

第4档 · 15 分钟
区间 DP dp[i][j] = dp[i+1][j−1] && s[i]==s[j],按长度从小到大枚举

二叉排序树计数 · BST

第1档 · 2 分钟
n == 0 或 n == 1 → 1

第2档 · 8 分钟
递归枚举每个根、累加左右子树方案,n ≤ 10 稳过

第3档 · 10 分钟
只做 n ≤ 10 的无记忆化递归

第4档 · 15 分钟
记忆化 f[n] = Σ f[i]·f[n−1−i](卡特兰数),开 long long

50 分钟时间盒(一题的节奏):
0–5 分钟 读题 + 圈出数据范围表里最小的那几档 → 5–15 分钟 写完第 1、2 档并先交一次(保住 20~30 分)→ 15–40 分钟 冲第 3、4 档 → 40–50 分钟 造小数据对拍、检查 freopen 与文件名。
课堂上别让学生"从头想正解"。先要求每个人 15 分钟内交出第 1、2 档, 再放开冲正解。这个顺序一换,平均分通常能涨一大截——因为它杜绝了"想了 40 分钟,交上去 0 分"。
检测

四道题

① 区间 DP 的第一层循环应该枚举什么?

② "abcda" 最少插入几个字符变成回文?

③ 4 个节点能构成几种不同形态的二叉排序树?

④ "abcd" 的最长回文子串长度是?

椰程锦囊:检测③有人会答 5(那是 n=3 的答案), 正好用来提醒"答案增长极快,必须开 long long";检测④的干扰项 3 来自"回文子序列"思路, 正好复习子串与子序列的区别。

目录