Decription
给定两个字符串,求他们最长的公共子序列长度,以及最长公共子序列个数。
n≤5000 n ≤ 5000
Solution
最长公共序列直接 O(n2) O ( n 2 ) DP即可。重点是如何求出最长公共子序列个数。
设 fi,j f i , j 表示第一个串匹配到 i i ,第二个串匹配到的最长公共子序列长度。 gi,j g i , j 则表示最长公共子序列个数。
考虑 fi,j f i , j 的转移 fi,j=max(fi−1,j,fi,j−1,(fi−1,j−1+1)×[si=tj]) f i , j = m a x ( f i − 1 , j , f i , j − 1 , ( f i − 1 , j − 1 + 1 ) × [ s i = t j ] )
那么 gi,j g i , j 则加上 fi,j f i , j 转移的来源的 g g 即可。
但是有一个特殊情况:
时, gi−1,j g i − 1 , j 与 gi,j−1 g i , j − 1 重复转移了 gi,j g i , j 因此要减去。
推荐FlashHu的题解,还有图示。
// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005, mod = 100000000;
int n, m, f[2][maxn], g[2][maxn];
char s[maxn], t[maxn];
void inc(int &a, int b)
{
a += b;
if (a >= mod) a -= mod;
}
void dec(int &a, int b)
{
a -= b;
if (a < 0) a += mod;
}
int main()
{
scanf("%s", s + 1); n = strlen(s + 1) - 1;
scanf("%s", t + 1); m = strlen(t + 1) - 1;
fill(g[0], g[0] + m + 1, 1);
for (int k = 1; k <= n; ++k) {
int i = k & 1;
g[i ^ 1][0] = g[i][0] = 1;
for (int j = 1; j <= m; ++j) {
f[i][j] = max(f[i ^ 1][j], f[i][j - 1]);
if (s[k] == t[j]) f[i][j] = max(f[i][j], f[i ^ 1][j - 1] + 1);
}
for (int j = 1; j <= m; ++j) {
g[i][j] = 0;
if (f[i][j] == f[i ^ 1][j]) inc(g[i][j], g[i ^ 1][j]);
if (f[i][j] == f[i][j - 1]) inc(g[i][j], g[i][j - 1]);
if (f[i][j] == f[i ^ 1][j - 1])
dec(g[i][j], g[i ^ 1][j - 1]);
if (f[i][j] == f[i ^ 1][j - 1] + 1 && s[k] == t[j])
inc(g[i][j], g[i ^ 1][j - 1]);
}
}
printf("%d\n%d\n", f[n & 1][m], g[n & 1][m]);
return 0;
}