线性DP—洛—两个排列的最长公共子序列
P1439 两个排列的最长公共子序列
题目描述
给出 \(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\)。
原来的想法
不知道变通,一味想着已有的模型。原来dp[i][2] 表示a序列第i个数加不加在最长子序列里面,但还要保留前面每个状态的最后的数在b序列的位置,判断怎么加。前面初值也比较难赋。若循环找前面能加的最长公共子序列,O(n^2),\(10^5\)会TLE,也不知道逻辑细节对不对。
代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n,ans;
int a[100010],b[100010],pos[100010]; //映射b的位置
int dp[100010][2][2]; //该位置不参与与参与最长自序列长度和最长子序列在b序列最后的位置
int main()
{
cin>>n;
for(int i = 1;i <= n;i++) cin>>a[i];
for(int i = 1;i <= n;i++)
{
cin>>b[i];
pos[b[i]] = i;
}
for(int i = 1;i <= n;i++)
{
dp[i][0][0] = max(dp[i-1][0][0],dp[i-1][1][0]);
dp[i][0][1] = (dp[i-1][1][0] > dp[i-1][0][0] ? dp[i-1][1][1] : dp[i-1][0][1]);
for(int j = i-1;j >= 0;j--)
{
if(pos[a[i]] > pos[dp[j][0][1]])
{
if(pos[a[i]] > pos[dp[j][1][1]] && dp[j][1][0] > dp[j][0][0])
{
dp[i][1][0] = dp[j][1][0] + 1;
}
else dp[i][1][0] = dp[j][0][0] + 1;
dp[i][1][1] = a[i];
break;
}
if(j == 0)
{
dp[i][1][0] = 1;
dp[i][1][1] = a[i];
}
}
}
//cout<<dp[1][1][1]<<endl;
for(int i = 1;i <= n;i++) ans = max(max(ans,dp[i][1][0]),dp[i][0][0]);
cout<<ans;
return 0;
}
/*
注意:观察题目,俩数列都是1~n的全排列
思想:有点贪心思想,每个数字可以加在最长子序列后面,找到前面符合条件的最长;也可以不使用该数字,状态存前一个数字的最大值
*/
正解
豆包提出转化为最长上升子序列,自己想了一下。如果映射B组数列,会有个1~n的映射,对应到A组就是最长上升子序列,跟之前的想法差不多,只是更加具体化了,序列A每个数字在序列B映射成先后顺序,小的在前面才可能公共。直接用贪心+二分,n*logn 级别。还是要多一些模型迁移能力。
代码:
点击查看代码
int n,ans;
int a[100010],b[100010],pos[100010]; //映射b的位置
vector<int> v;
int main()
{
cin>>n;
for(int i = 1;i <= n;i++) cin>>a[i];
for(int i = 1;i <= n;i++)
{
cin>>b[i];
pos[b[i]] = i;
}
v.push_back(pos[a[1]]);
for(int i = 2;i <= n;i++)
{
if(pos[a[i]] > v.back()) v.push_back(pos[a[i]]);
else
{
auto it = upper_bound(v.begin(),v.end(),pos[a[i]]);
*it = pos[a[i]];
}
}
cout<<v.size();
return 0;
}

浙公网安备 33010602011771号