GDUT - 专题学习2 B - 最长公共子序列

本文介绍了一种解决最长公共子序列问题的有效算法,通过二分查找优化至O(nlogn)的时间复杂度,适用于大规模数据处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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中的最长升序子序列为 bcac 更巧的是其代表的元素 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值