题意:
给定两个字符串,求两串的最长公共子序列(LCS问题)
思路:
直接上dp分析:
f[i,j] 状态表示:
集合:所有 a[1 ~ i]
与 b[1 ~ j]
的 公共子序列 的集合
属性:长度的最大值 max
状态计算:
集合划分:
依据最后一个不同点,我们可以分为 4 类:
- ①
a[i]、b[j]
均存在于最长公共子序列(11) - ②
a[i]、b[j]
均不存在于最长公共子序列(00) - ③
a[i]
不存在,而b[j]
存在(01) - ④
a[i]
存在,而b[j]
不存在(10)
对各类分别求得 max
,汇总起来再取 max
,即为 f[i,j]
的答案。
第 ① 类很好求,其存在的条件当然是 a[i]==b[j]
,根据定义,此类的最大值为:f[i-1,j-1] + 1
。
第 ② 类根据定义也很显然,为:f[i-1,j-1]
。
第 ③ 类就需要好好分析了:
先尝试用式子 f[i-1,j]
对该类进行计算,但 f[i-1,j]
并不完全等价于这一类,
因为此类所指的是:“a[i]
不包含,而 b[j]
包含”,
而 f[i-1,j]
表示:在 a
串的 1 ~ i-1
中 和 b
串的 1 ~ j
中 选择的最长公共子序列,一定不包含 a[i]
,可能包含 b[j]
也可能不包含,b[j]
的存在情况有两种,简而言之,f[i-1,j]
表示的集合可以分为 两类: “含 b[j]
” 和 “不含 b[j]
” 。
第 ③ 类集合实际上是与 f[i-1,j]
分的第一类(含 b[j]
)是恰好完全等价的
那有没有一个式子能够 恰好覆盖 (直接计算)这一类呢?我们发现是没有的。
但是我们知道,对于求一个集合的 数量属性 时,我们要保证 “不重不漏”,但是 求最值属性 的时候我们只要保证 “不漏” 即可,“不重” 其实是无所谓的,不影响最终的答案。
因此我们再求第 ③ 类最大值的时候,我们就可以直接用 f[i-1,j]
来覆盖(注意:并不是直接计算出这一类),f[i-1,j]
表示集合完全包含第 ③ 类集合,且一定在 f[i,j]
表示的总集合内部。
第 ④ 类,同理,分析和第 ③ 类同理,可以用 f[i,j-1]
表示。
我们发现,对于第 ② 类,它是 既包含于第 ③ 类,又包含于第 ④ 类,所以再求最大值时,只需要关注其它三类 即可。(优化之处)
这样一来,我们就可以 覆盖所有类,求得 整个集合的最大值(答案)
综上,我们得出 状态转移方程:
f
[
i
,
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
−
1
]
+
1
)
f[i,j] = max(f[i-1][j],f[i][j-1],f[i-1][j-1] + 1)
f[i,j]=max(f[i−1][j],f[i][j−1],f[i−1][j−1]+1)(三类,其中第 3
项在 a[i]==b[j]
时才存在)
时间复杂度:
O
(
n
m
)
O(nm)
O(nm)(n、m
分别为两个字符串的长度)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int dp[N][N];
int n, m;
char a[N], b[N];
int main()
{
cin>>n>>m>>a+1>>b+1;
for(int i=1; i<=n; ++i)
{
for(int j=1; j<=m; ++j)
{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
if(a[i]==b[j]) dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1); //当a[i]==b[j]第三类才存在
}
}
cout<<dp[n][m]<<endl;
return 0;
}