dp快解最长公共子序列(LCA)

今天跟大家分享一道很经典的dp题——最长公共子序列


乍一看这道题,貌似就是一个很简单的dp就可以了。粽锁粥汁,dp最重要的就是状态和方程。状态不难找。给你两串数字,让你求公共的序列,我们就可以设一个二维dp数组,dp[i][j]可以设为第一个串的前i个元素和第二个串的前j个元素的公共子序列。方程的写出也不甚困难。写方程只要找到前一个状态就可以,现在对于新加进来的第i、j个数,只要相等就可以把他加入我们的公共子序列,得到的序列长度就是没加这个数进来时的序列长度+1。于是乎,一个简简单单的代码就这么写出来了。

#include<bits/stdc++.h>
using namespace std;
int n,a[10001],b[10001],dp[10001][10001];
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++)
		{
			if(a[i]==b[j])dp[i][j]=max(dp[i-1][j],dp[i][j-1])+1;
			else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
		}
	}
	printf("%d",dp[n][n]);
	return 0;
}

但可惜,看了看这题的限制,n\leq 10^5,我的二维dp数组每一维都要十的五次方那么大,百分百会爆内存。

于是,我们的方案2就出炉了。

由于公共子序列的顺序一定要是固定的,所以这题我们可以换一个思路。有两个串,对吧?而这题有一个很有用的要求。两串数的构成都一样,只不过数的顺序不一样。于是,我们可以用一个数组先存下第二个串的各个元素在第一个串里的位置(下标),存在一个数组里,然后,只需要求出它的最大上升子序列就可以啦!

我们把这个问题从一个dp问题转换成了另一个dp问题。最大上升子序列也是另一到较为经典的动态规划题。

我们来回顾一下求上升子序列长度的算法。

我们的第一种方法的状态数组dp[i]代表的是以第i个元素结尾的最大上升子序列的长度。(有没有发现这个算法只需要\Theta (n)的辅助空间)。首先,先赋初始值

dp[1]=1;                //第一个元素自己组成一个上升序列
int ans=-0x3f3f3f3f;    //用来存储答案

 方程也可以很快写出来。就是当我在为第i(i≥2)个元素的长度赋值时,我就在0~i-1这个区间中找一个j,在序列中数值比第i个位置上的数值小且dp[j]最大,然后把这个最大长度加上1再赋给dp[i]就可以了。

#include<bits/stdc++.h>
const int MAXN=1e5+4;
using namespace std;
int n;
int datp[MAXN],pos[MAXN];
int dp[MAXN];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int t;
		scanf("%d",&t);
		datp[t]=i;
	}
	for(int i=1;i<=n;i++)
	{
		int t;
		scanf("%d",&t);
		pos[i]=datp[t];
	}
	if(pos[1]!=0)dp[1]=1;
	int ans=0;
	for(int i=2;i<=n;i++)
	{
		int maxx=0;
		for(int j=1;j<i;j++)
		{
			if(pos[j]<pos[i])maxx=max(maxx,dp[j]);
		}
		dp[i]=maxx+1;
		ans=max(ans,dp[i]);
	}
	printf("%d",ans);
	return 0;
}

但这个代码的时间复杂度为\Theta(n^2),当n在10的五次方时一定会超时。回顾程序,我们在找最小值的时候花了不少时间。有没有更好的方法呢?

有!

我们来看一下优化后的算法。

首先,一提到查找优化,我们马上就可以想到二分查找算法。但很令人头疼的是,二分查找有一个条件,就是待查找元素必须严格具有单调性。于是,我们不妨把这题的dp数组改良一下。让dp数组来存储子序列的各个元素不就好了嘛(→_→)。所以,因为上升子序列是单调的,所以当我们接到一个新的序列元素时,如果这个元素比系列的最后一个元素大,我们可以选择把子序列的长度加一,否则就用二分查找来寻找这个元素的插入位置,随后把这个位置上的值替换成我们当前判断的元素,同时还要保持序列单调。最后,我们的最终AC代码(\Theta (nlog_{2}n)的复杂度)就出炉啦!

注:len是子序列长度

#include<bits/stdc++.h>
const int MAXN=1e5+4;
using namespace std;
int n;
int datp[MAXN],pos[MAXN];
int dp[MAXN];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int t;
		scanf("%d",&t);
		datp[t]=i;
	}
	for(int i=1;i<=n;i++)
	{
		int t;
		scanf("%d",&t);
		pos[i]=datp[t];
	}
	int len=1;
	dp[1]=pos[1];
	for(int i=2;i<=n;i++)
	{
		if(pos[i]>dp[len])dp[++len]=pos[i];
		else
		{
			int l=1,r=len;
			while(l<r)
			{
				int mid=(l+r)/2;
				if(dp[mid]<pos[i])l=mid+1;
				else r=mid;
			}
			dp[l]=pos[i];
		}
	}
	printf("%d",len);
	return 0; 
}

正文内容就到这里,最后投个票。

dijkastra和dp哪个难?

点赞加关注,谢谢大家~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值