最长公共子序列(LCS)问题
给定两个 1 到 n 的排列 A,B (即长度为 n 的序列,其中 [1,n] 之间的所有数都出现了恰好一次)。
求 它们的最长公共子序列长度。
子序列: 一个序列A = a1,a2,……an,中任意删除若干项,剩余的序列叫做A的一个子序列。也可以认为是从序列A按原顺序保留任意若干项得到的序列。
求解算法
对于母串X=<x1,x2,⋯,xm>, Y=<y1,y2,⋯,yn>,求LCS与最长公共子串。
暴力解法
假设 m<n, 对于母串X,我们可以暴力找出2的m次方个子序列,然后依次在母串Y中匹配,算法的时间复杂度会达到指数级O(n∗2的m次)。显然,暴力求解不太适用于此类问题。
动态规划
假设Z=<z1,z2,⋯,zk>是X与Y的LCS, 我们观察到
如果Xm=Yn,则Zk=Xm=Yn,有Zk−1是Xm−1与Yn−1的LCS;
如果Xm≠Yn,则Zk是Xm与Yn−1的LCS,或者是Xm−1与Yn的LCS。
因此,求解LCS的问题则变成递归求解的两个子问题。但是,上述的递归求解的办法中,重复的子问题多,效率低下。改进的办法——用空间换时间,用数组保存中间状态,方便后面的计算。
先假设有 C[i,j] = | LCS(x[1…i] , y(1…j)) |,则有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| // n:表示两序列长度 // a:描述序列 a(这里需要注意的是,由于 a 的下标从 1 开始,因此 a[0] 的值为-1,你可以忽略它的值,只需知道我们从下标 1 开始存放有效信息即可) // b:描述序列b(同样地,b[0] 的值为 -1) // 返回值:最长公共子序列的长度 #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> #include <queue> #include <stack> #include <map> #include <set> #include <vector> #include <iostream> #include <utility> using namespace std; #define PII pair<int,int> #define PIII pair<pair<int,int>,int> #define mp make_pair #define pb push_back #define sz size() #define fi first #define se second typedef unsigned int ui; typedef long long ll; typedef unsigned long long ull; vector<int> pos,a,b,f; int getAns(int n,vector<int>a,vector<int>b){ f.resize(n+1);pos.resize(n+1); for(int i=0;i<=n;++i) f[i]=n+2; for(int i=1;i<=n;++i){ pos[b[i]]=i; } for(int i=1;i<=n;++i){ a[i]=pos[a[i]]; } f[0]=0; for(int i=1;i<=n;++i) *lower_bound(f.begin(),f.end(),a[i])=a[i]; return int(lower_bound(f.begin(),f.end(),n+1)-f.begin())-1; } int main(){ int n;scanf("%d",&n); a.resize(n+1);b.resize(n+1); for(int i=1;i<=n;++i){ scanf("%d",&a[i]); } for(int i=1;i<=n;++i){ scanf("%d",&b[i]); } printf("%d\n",getAns(n,a,b)); return 0; }
|
#输出结果