最长公共子序列
最长公共子序列是一个典型的二维线性动态规划问题,给定两个长度相同的序列,求这两个序列中的最长公共子序列。
例如:序列 1 , 3 , 5 , 4 , 9 1,3,5,4,9 1,3,5,4,9和 5 , 7 , 4 , 9 , 3 5,7,4,9,3 5,7,4,9,3的最长公共子序列为 5 , 4 , 9 5,4,9 5,4,9。
动态规划
定义状态为 d p [ i ] [ j ] dp[i][j] dp[i][j]为子序列 a a a到下标 i i i为止,子序列 b b b到下标 j j j为止的最长公共子序列的长度。因此我们有如下的动态规划方程:
如果 a [ i ] = b [ j ] a[i] = b[j] a[i]=b[j]:
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i−1][j−1]+1
即为,如果该位相等,那么本状态就等于 a b ab ab的上一位加 1 1 1。
如果 a [ i ] ≠ b [ j ] a[i] \neq b[j] a[i]=b[j]:
d p [ i ] [ j ] = max ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = \max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i−1][j],dp[i][j−1])
即为,如果该位不相等,那么就状态继承,两个同位状态决策出最优的状态。
#include <bits/stdc++.h>
#define FR freopen("in.txt","r",stdin);
using namespace std;
int a[100];
int b[100];
int dp[100][100];
int main()
{
int n;
cin >> n;
for(int i = 0; i<n; i++)
cin >> a[i];
for(int i = 0; i<n; i++)
cin >> b[i];
for(int i =0; i<n; i++)
for(int j =0; j<n; j++)
if(a[i] == b[j])
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
cout << dp[n][n];
return 0;
}
特例
观察这个题的数据范围,发现上述 O ( n 2 ) O(n^{2}) O(n2)的算法必然TLE,那么我们只能转换思路。
例如序列:
3 , 2 , 1 , 4 , 5 1 , 2 , 3 , 4 , 5 3,2,1,4,5 \\ 1,2,3,4,5 3,2,1,4,51,2,3,4,5
我们知道最长的公共子序列一定是 b b b的子序列,同时也是 a a a的子序列,那么问题就转换成在 b b b中找到 a a a的最长上升子序列即可。跟普通的最长上升子序列问题不同,排序的关键字是元素在 a a a序列中的下标位置即转换成:
3 0 , 2 1 , 1 2 , 4 3 , 5 4 1 2 , 2 1 , 3 0 , 4 3 , 5 4 3_{0},2_{1},1_{2},4_{3},5_{4} \\ 1_{2},2_{1},3_{0},4_{3},5_{4} 30,21,12,43,5412,21,30,43,54
因此就是求 2 , 1 , 0 , 3 , 4 2,1,0,3,4 2,1,0,3,4的最长上升子序列即可。
#include <bits/stdc++.h>
#define FR freopen("in.txt","r",stdin);
using namespace std;
int idx[100005];
int g[100005];
int b[100005];
int main()
{
FR
int n;
cin >> n;
for(int i = 0; i<n; i++)
{
int val;
cin >> val;
idx[val] = i;
}
for(int i = 0; i<n; i++)
cin >> b[i];
g[1] = idx[b[0]];
int len = 1;
for(int i = 1; i<n; i++)
{
if(idx[b[i]] > g[len])
{
g[++len] = idx[b[i]];
}
else
{
int loc = lower_bound(g+1,g+len,idx[b[i]]) - g;
g[loc] = idx[b[i]];
}
}
cout << len;
return 0;
}