8.19 求解最长子序列

最长子序列,是一种典型的线性dp问题。

最长公共子序列

最长公共子序列(LCS)是一个在一个序列集合中(通常为两个序列)用来查找所有序列中最长子序列的问题。一个数列,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则称为已知序列的最长公共子序列。

基本思想

根据定义发现,最长公共子序列不严格要求两个序列公共部分连续,只要其出现顺序一致即可,考虑分类讨论。
将S1表示为a[N],S2表示为b[N],LCS表示为dp[N][N],解题思路如下:

1.两个序列的最后一个元素相等
那么两个序列的LCS即为S1减去最后一个元素,与S2减去最后一个元素的LCS加上共同的最后一个元素。得到状态转移方程:
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − 1 ] + 1 ) dp[i][j] = max(dp[i][j],dp[i-1][j-1]+1) dp[i][j]=max(dp[i][j],dp[i1][j1]+1)

2.两个序列的最后一个元素不等
令S1减去最后一个元素,与S2的LCS为L1,S2减去最后一个元素,与S1的LCS为L2,
则两序列的LCS就等于max(L1,L2)。得到状态转移方程:
d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) dp[i][j] = max(dp[i][j-1],dp[i-1][j]) dp[i][j]=max(dp[i][j1],dp[i1][j])

朴素版

#include<bits/stdc++.h>
using namespace std;

const int N = 1005;
int a[N],b[N],dp[N][N];

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=n;i++)
	{
		cin>>b[i];
	}
	
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
			if(a[i]==b[j]) dp[i][j] = max(dp[i][j],dp[i-1][j-1]+1);
	
		}
	}
	
	cout<<dp[n][n];
}

最长上升子序列

最长上升子序列(Longest Increasing Subsequence,LIS),在计算机科学上是指一个序列中最长的单调递增的子序列。

朴素版

朴素版解法的思路很简单,设置一个dp数组,循环每一个数之前的所有数字。
如果数字比自己小,当前位置的dp就取max(当前位置子序列长度,该数字子序列长度+1,即加上它本身长度),最后再循环找出dp数组的最大值。

#include<iostream>
using namespace std;
const int N = 5e3+5;

long long arr[N],dp[N];

int main()
{
	int n;
	cin>>n;
	
	for(int i=1;i<=n;i++)
	{
		cin>>arr[i];//在线处理 
		dp[i] = 1;
		for(int j=1;j<=i;j++)
		{
			if(arr[i]>arr[j]) dp[i] = max(dp[i],dp[j]+1);
		}
	}
	
	int ans = 0;
	for(int i=1;i<=n;i++)
	{
		if(ans<dp[i]) ans = dp[i];
	}
	cout<<ans;
	
}

单调栈优化

上文介绍的解法确实易读,但比赛的题目数据可是很恶毒,朴素版解法很容易超时。那么,介绍一下单调栈优化。

优化原理

将可转移的状态存入一个单调队列,并不断对其进行维护+状态转移。
本质: 如果你的队友比你年轻比你强,那你就可以退役了
这里的单调栈不需要出,只需要不断判断加入,就可以写成栈的形式。

每次加入一个数时,判断它是否比栈顶的数大:
是, 直接入栈
否, 二分查找lower_bound,替换掉那个位置的数。
因为前面的数越大,后面的子序列的长度就会越长。
注:求出的最长子序列长度是正确的,但栈内不一定就是那个长度最大的最长上升子序列
改一改就变成最长下降子序列啦!

模板代码

#include<iostream>
using namespace std;

const int N = 1e5+5;
int arr[N],stk[N];
//stk[i] 存长度为i的上升子序列的最小结尾

int lower_bound(int stk[],int l,int r,int x)
{
	while(l < r)
	{
		int mid = l+r >> 1;
		
		if(stk[mid] >= x)
		{
			r = mid;
		}
		else
		{
			l = mid+1;
		}	
	}
	return l;	
}

int main()
{
	int n;
	cin>>n;
	
	for(int i=1;i<=n;i++)
	{
		cin>>arr[i];
	}
	
	int j = 0;
	for(int i = 1;i <= n;i++)
	{
		if(!j || arr[i] > stk[j]) stk[++j] = arr[i];
		else
		{
			int l = lower_bound(stk, 1, j, arr[i]);
			stk[l] = arr[i];
		}
	}
	cout<<j;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值