【题解】【模板】最长公共子序列(LCS)

题目描述

给出 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;
}

猜你喜欢

转载自blog.csdn.net/weixin_45485187/article/details/109243027
今日推荐