B - 最长公共子序列
题目
给出 1∼n 的两个排列 P1 和 P2,求它们的最长公共子序列。
输入格式
第一行是一个数 n (1≤n≤105)。
接下来两行,每行为 n 个数,为自然数 1∼n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
Sample Input
5 3 2 1 4 5 1 2 3 4 5
Sample Output
3
思路
1.dp做法 O(n²)
首先要知道,对于单独的一个元素来说,其最长上升子序列就是它本身,所以dp的每一个元素一开始都应该是1
然后dp[i]可用于记录在 i 之前的每一个元素的最优解,如果当前的dp[i]大于之前的最优解,即可继承下去
for(int i = 1; i <= n; ++i)
{
dp[i] = 1;//对dp数组进行初始化
for(int j = 1; j < i; ++j)
{
if(a[j] < a[i] && dp[i] < dp[j] + 1)//先判断该是否为上升子序列,再判断子序列是否更长
{
dp[i] = dp[j] + 1;//如果是,则继承结果并加一
}
}
}
在本题的条件中,10^5显然会超时,所以此方法在本题不适用。
2.二分查找做法 O(nlogn)
仔细观察题目发现,两个序列都是从1到n的全排列,所以两个序列的元素都是相同的,只是位置不一样而已,这时候我们再用一个数组t将序列P1中的元素在序列P2中的位置表示出来。为什么呢?
假设有以上两个序列,为了更加直观我用小写字母按从小到大的顺序来表示P1中各元素
这个时候 a—1 b—3 c—4 d—2 e—5
再用这些字母去替代P2
这时候P2序列就变成了edbac,不难发现P2中的最长升序子序列为 bc 或 ac ,更巧的是其代表的元素 34 和 14 正好也是P1中的子序列,当然这不是个巧合,同学们深思一下就能想明白其中的道理了
这时候题目就变得简单了,我们只需再用一个数组存P1对应元素的顺序
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
t[a[i]] = i;//将各元素所在位置存起来
}
再用这个顺序代替P2,最后在变化后的P2中找出其最长上升子序列就可以了,用二分随便搞搞就做完了。其中找最长上升子序列的方法在这里有细讲。
代码
#include<iostream>
#include<algorithm>
using namespace std;
int n, p1[100005], p2[100005], t[100005], f[100005], len = 0;
//数组t用于存放p1中元素的位置,t[2] = 1就表示元素2在p1中出现的位置时第1位
//数组f则是用来存放替换后的数组p2
int main()
{
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> p1[i];
t[p1[i]] = i;
}
for (int i = 1; i <= n; ++i)
{
cin >> p2[i];
}
for (int i = 1; i <= n; ++i)
{
if (t[p2[i]] > f[len])//如果该元素大于上一个,说明可以接上去,然后再令最大值等于该元素
{
f[++len] = t[p2[i]];
}
else//如果不是,则找数组f中第一个大于等于该元素的元素位置,并将其接在后面
{
f[lower_bound(f + 1, f + len + 1, t[p2[i]]) - f] = t[p2[i]];
}
}
cout << len;//最后输出最长的len
return 0;
}