最长公共子序列

简介

LCS

算法

首先我们看一下最简单且使用的 $O(n^2)$ 做法

我们令 f[i][j] 表示第一个序列匹配到第i个,第二个序列匹配到第j个时的最长公共子序列的长度

转移直接继承或者是ij匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <cstdio>
#define maxn 1010
using namespace std;

int n, a[maxn], b[maxn];

int f[maxn][maxn];

int main() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= n; ++i) cin >> b[i];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i] == b[j]) f[i][j] = f[i - 1][j - 1] + 1;
}
cout << f[n][n] << endl;
return 0;
}

另外我们考虑如何处理序列中没有相同元素的特殊情况,能否将时间复杂度优化到 $O(n^2)$ 以下

我们考虑在原方法当且仅当a[i]==b[j]时才会增加f的值

所以我们考虑在这方面进行优化

我们对第二个数组做一个类似hash的东西

我们将每个数变成在第一个数组中出现该数的下标

那么问题被转换成了LIS问题

时间复杂度 $O(nlogn)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <cstdio>
#include <algorithm>
#define maxn 100010
using namespace std;

int n, a[maxn], b[maxn], p[maxn];

int d[maxn], len;

int main() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i], p[a[i]] = i;
for (int i = 1; i <= n; ++i) cin >> b[i], b[i] = p[b[i]];
for (int i = 1; i <= n; ++i) {
if (b[i] > d[len]) d[++len] = b[i];
else *lower_bound(d + 1, d + len + 1, b[i]) = b[i];
} cout << len << endl;
return 0;
}