双序列 DP
---
算法简介
双序列 DP 主要用于解决两个线性结构之间的匹配、转换或对比类问题。核心思想是定义状态 $dp[i][j]$ 表示序列 $A$ 的前 $i$ 个元素与序列 $B$ 的前 $j$ 个元素之间的最优解(如最大相似度、最小转换代价等)。
- 时间复杂度:通常为 $O(N \times M)$
- 基本枚举顺序:双层嵌套循环,外层枚举 $A$ 的下标 $i$,内层枚举 $B$ 的下标 $j$。
- 状态转移:
- 路径输出
- 空间优化
- 方案计数
------
一、LCS 最大匹配模型
1. 核心原理
这是最基础的模型,侧重于找共性。特点是字符不要求连续,只要求相对顺序一致。
当考虑 $A[i]$ 和 $B[j]$ 时:
- 若相等,则贡献来源于左上角(延续匹配)。
- 若不等,则贡献来源于左方或上方(继承历史最优)。
$$dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 & \text{if } A[i] == B[j] \\ \max(dp[i-1][j], dp[i][j-1]) & \text{if } A[i] \neq B[j] \end{cases}$$
------
2. 常用技巧
记录转移来源(或在填表后逆向倒推):若 $A[i]==B[j]$ 则向左上走,否则向 $dp$ 值较大的方向走。
由于 $dp[i]$ 只依赖于 $dp[i-1]$,可利用滚动数组将空间优化至 $O(\min(N, M))$。
若题目求“有多少种匹配方案”,将 max 改为 sum,且 $A[i] \ne B[j]$ 时通常只继承 $dp[i-1][j]$(取决于题目定义的删除规则)。
------
3. 代码模板
// LCS 通用模板
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (A[i] == B[j]) {
// 匹配成功:长度加 1
dp[i][j] = dp[i-1][j-1] + 1;
} else {
// 匹配失败:谁大听谁的
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}------
二、编辑距离最小代价模型
1. 核心原理
这类问题侧重于做变换。通过增、删、改三种操作将 $A$ 变成 $B$,求最小代价。
$$dp[i][j] = \min \begin{cases} dp[i-1][j] + \text{DelCost} \\ dp[i][j-1] + \text{InsCost} \\ dp[i-1][j-1] + (A[i]==B[j] ? 0 : \text{RepCost}) \end{cases}$$
------
2. 常用技巧
这是与 LCS 最大的不同点。
$dp[i][0] = i \times \text{DelCost}$ (把 $A$ 删光)
$dp[0][j] = j \times \text{InsCost}$ (从空串插入出 $B$)
题目可能规定删除 'a' 的代价是 100,删除 'b' 是 200(如 ASCII 删除和),此时 Cost 需动态计算。
------
3. 代码模板
// 最小编辑距离模板
// 1. 初始化边界
for (int i = 1; i <= n; i++) dp[i][0] = i;
for (int j = 1; j <= m; j++) dp[0][j] = j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (A[i] == B[j]) {
dp[i][j] = dp[i-1][j-1]; // 无代价
} else {
// 决策:删、增、替 中取最小
dp[i][j] = min({
dp[i-1][j] + 1, // 删除
dp[i][j-1] + 1, // 插入
dp[i-1][j-1] + 1 // 替换
});
}
}
}------
三、连续子串模型
1. 核心原理
这类问题的核心是连贯性。与 LCS 的区别在于:一旦当前字符不匹配,之前的积累必须清零。
$$dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 & \text{if } A[i] == B[j] \\ 0 & \text{if } A[i] \neq B[j] \end{cases}$$
------
2. 常用技巧
最终答案不是 $dp[n][m]$,而是矩阵中的全局最大值 $\max(dp[i][j])$。
由于只需要 $dp[i-1][j-1]$,可以用一维数组从后往前遍历来优化空间。
------
3. 代码模板
// 最长公共子串模板
int maxLen = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (A[i] == B[j]) {
dp[i][j] = dp[i-1][j-1] + 1;
maxLen = max(maxLen, dp[i][j]); // 实时更新全局最大
} else {
dp[i][j] = 0; // 核心:断裂归零
}
}
}------
【前置衔接知识点】
1、动态规划基础
【思维导图】

【题目知识点分类】
01
[模版]最长公共子序列
普及-
--
练习
02
编辑距离
普及/提高-
--
练习
03
最长公共子序列(输出路径)
普及-
--
练习
04
数字连线游戏
普及-
--
练习
05
古老卷轴的修复
普及-
--
练习
06
单词的最小修改
普及/提高-
--
练习
07
完美的洗牌
普及/提高-
--
练习
08
序列匹配
普及/提高-
--
练习
09
字符的清理代价
普及/提高-
--
练习
10
四大宝石的共鸣
普及/提高-
--
练习
11
字符串大师
提高+/省选-
--
练习
12
论文查重
普及+/提高
--
练习
13
信号的最佳共鸣
普及/提高-
--
练习
14
潜藏的指令
普及/提高-
--
练习
15
公共子序列对
普及/提高-
--
练习
16
修复古老的预言
普及+/提高
--
练习
17
通配符匹配
普及+/提高
--
练习
18
字符串混合
普及/提高-
--
练习
19
净化传输信号
普及+/提高
--
练习
20
「NOIP2015」子串
普及+/提高
--
练习
21
排列的最长公共子序列
普及+/提高
--
练习
22
LCIS
提高+/省选-
--
练习
23
编辑距离(pro 版本)
提高+/省选-
--
练习
24
[NOIP2023] 双序列拓展
省选/NOI-
--
练习