## 题目描述
给出 $1,2,\ldots,n$ 的两个排列 $P_1$ 和 $P_2$ ,求它们的最长公共子序列。
## 输入格式
第一行是一个数 $n$。
接下来两行,每行为 $n$ 个数,为自然数 $1,2,\ldots,n$ 的一个排列。
## 输出格式
一个数,即最长公共子序列的长度。
## 样例 #1
### 样例输入 #1
```
5
3 2 1 4 5
1 2 3 4 5
```
### 样例输出 #1
```
3
```
## 提示
- 对于 $50\%$ 的数据, $n \le 10^3$;
- 对于 $100\%$ 的数据, $n \le 10^5$。
做法一:score:50(dp加暴力做法),但是会超时
dp[i][j]表示取第一个串的从1到第i位元素和取第二个串的从1到j位元素的最长公共子序列
用二重循环遍历i,j
如果元素相等a1[i]==a2[j] 就更新长度 dp[i][j]=dp[i-1][j-1]+1;
如果不相等,就继承最长的状态 dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
最后输出dp[n][n]即为答案
代码如下:(这里的m就是n,当时没改)
#include<iostream>
using namespace std;
int dp[1001][1001],a1[2001],a2[2001],n,m;
int main()
{
//dp[i][j]表示两个串从头开始,直到第一个串的第i位
//和第二个串的第j位最多有多少个公共子元素
cin>>n;
m=n;
for(int i=1;i<=n;i++)scanf("%d",&a1[i]);
for(int i=1;i<=m;i++)scanf("%d",&a2[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(a1[i]==a2[j])
dp[i][j]=dp[i-1][j-1]+1;
//因为更新,所以++;
}
cout<<dp[n][m];
}
做法二:满分做法(用映射和最长上升子序列的思想)
下面的说明来自洛谷的一个高手的解释,在此引用一下
A:3 2 1 4 5
B:1 2 3 4 5
我们不妨给它们重新标个号:把3标成a,把2标成b,把1标成c……于是变成:
A: a b c d e
B: c b a d e
这样标号之后,LCS长度显然不会改变。但是出现了一个性质:
两个序列的子序列,一定是A的子序列。而A本身就是单调递增的。
因此这个子序列是单调递增的。
换句话说,只要这个子序列在B中单调递增,它就是A的子序列。
哪个最长呢?当然是B的LIS最长。
自此完成转化 //来自题解 P1439 【【模板】最长公共子序列】 - 洛谷专栏 (luogu.com.cn)
即先把第一个串的元素按顺序映射为1,2,3,4...
然后把第二串的数组元素根据上面的映射,改变他们相应的值,最后即求改变后的b数组的最长上升子序列长度即可,代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N],n,Index=1;
unordered_map <int,int> m;
bool check(int mid,int x){
if(b[mid]<=x)
return true;
return false;
}
int leng(int b[],int ll){
int cnt=1;
b[1]=b[1];
for(int i=2;i<=ll;i++){
if(b[i]>b[cnt]){
cnt++;
b[cnt]=b[i];
}
else if(b[i]<b[cnt]){
int l=1,r=cnt;
while(l<r){
int mid=l+r>>1;
if(check(mid,b[i]))
l=mid+1;
else r=mid;
}
// printf("i=%d l=%d cnt=%d\n",i,l,cnt);
b[l]=b[i];
}
}
return cnt;
}
//上面为求最长上升子序列长度的函数
int main(){
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
m[a[i]]=Index;
Index++;
}
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
b[i]=m[b[i]];//求b数组的最长上升子序列长度
printf("%d",leng(b,n));
return 0;
}