题目描述
给出 1,2,…,n 的两个排列P1和P2,求它们的最长公共子序列。
输入格式
第一行是一个数 n。
接下来两行,每行为 n 个数,为自然数1,2,…,n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
输入输出样例
输入
5
3 2 1 4 5
1 2 3 4 5
输出
3
说明/提示
对于 50% 的数据, n≤10^3
对于 100% 的数据,n≤10^5
思路
对于50%的数据,可以考虑动态规划,设dp[i][j]表示子序列Ai和Bi的最长公共子序列的长度
当Ai = Bi时,找出Ai-1和Bi-1的最长公共子序列,然后在其尾部加上Ai即可得到A和B的最长公共子序列
当Ai != Bi 时,求解两个子问题:
1、求Ai-1和Bi的最长公共子序列
2、求Ai和Bi-1的最长公共子序列
然后取1、2中的最大值
即
Ai=Bi -> dp[i][j]= dp[i-1][j-1]+1
Ai!=Bi -> dp[i][j]=max(dp[i][j-1],dp[i-1][j])
DP代码
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int dp[N][N],a[N],b[N],n;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) scanf("%d",&b[i]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;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);
}
cout<<dp[n][n]<<endl;
return 0;
}
由于上面的代码用到了双重循环,时间复杂度为O(n^2),对于100%的数据是不行的,于是一位洛谷大佬站了出来,说了下面这一段话:
因为两个序列都是 1~n的全排列,那么两个序列元素互异且相同,也就是说只是位置不同罢了,那么我们通过一个 map数组将 A序列的数字在 B序列中的位置表示出来——
因为最长公共子序列是按位向后比对的,所以a序列每个元素在b序列中的位置如果递增,就说明b中的这个数在a中的这个数整体位置偏后,可以考虑纳入 LCS——那么就可以转变成 nlogn求用来记录新的位置的map数组中的 LIS。
巧妙地将LCS(最长公共子序列)转换成了LIS(最长递增子序列)
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,len,a[N],m[N],b[N],f[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
m[a[i]]=i;
}
for (int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
f[i]=99999999;
}
f[0]=0;
for (int i=1;i<=n;i++)
{
if (m[b[i]]>f[len]) f[++len]=m[b[i]];
else
{
/*int l=0,r=len;
while (l<r)
{
int mid=(l+r)/2;
if (f[mid]>m[b[i]]) r=mid;
else l=mid+1;
}
f[l]=min(m[b[i]],f[l]);*/
int k=lower_bound(f+1,f+1+len,m[b[i]])-f; //这段代码相当于上面的一串二分查找,就是寻找f[]中第一个大于等于m[b[i]]的数的位置
f[k]=min(m[b[i]],f[k]);
}
}
cout<<len<<endl;
return 0;
}