浅谈最长上升子序列的二分优化

浅谈最长上升子序列的二分优化

最长上升子序列=LIS。

这人在学习 LIS 后一年多才会 LIS 的二分优化,是不是废了。。

省流:贪心+二分,\(n^2\rightarrow n\log n\)

大概题意

给定一个长度为 \(n\) 的序列 \(a\),求 \(a\) 的最长上升子序列长度。

定义一个长度为 \(k\) 的序列 \(b\) 是最长上升子序列,当且仅当 \(b_1<b_2<b_3\cdots b_{k-1}<b_k\)

普通动态规划方法

\(f_i\) 表示以 \(a_i\) 为结尾的最长 LIS 长度。则转移方程为 \(f_i=\max_{j=1}^{i-1}\left \{f_j+1(a_j<a_i)\right\}\)

意为在下标为 \([1,i-1]\) 中枚举每一个满足 \(a_j<a_i\) 的数,尝试在 \(a_j\) 后接入 \(a_i\) 所带来的 LIS 长度。

时间复杂度为 \(O(n^2)\)。有时候并不能过。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005;
int n,ans;
int a[MAXN],f[MAXN];
inline int Read()
{
	int num=0,r=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			r=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		num=num*10+ch-'0';
		ch=getchar();
	}
	return num*r;
}
int main(){
	n=Read();
	for(int i=1;i<=n;i++)
	{
		a[i]=Read();
		f[i]=1;
	}
	for(int i=2;i<=n;i++)
		for(int j=1;j<i;j++)
			if(a[i]>a[j])
				f[i]=max(f[i],f[j]+1);
	for(int i=1;i<=n;i++)
		ans=max(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}

二分+贪心优化

考虑这么一件事情:

如果有两个长度为 \(k\) 的 LIS,一个结尾为 \(c\),一个结尾为 \(d\),且 \(c<d\)。那么你会如何更新当前的 \(f_i\)?

显然因为 \(c<d\),那么可以用 \(d\) 来更新的 LIS 一定可以被 \(c\) 更新。所以考虑贪心。在 LIS 长度相同的情况下,只保留末尾数字最小的。

所以我们可以再开一个数组 \(g\)\(g_i\) 表示 LIS 长度为 \(i\) 的最小的结尾数字。

那么我们更新 \(f_i\) 的时候就可以二分找到 \(g\) 的第一个大等于 \(a_i\) 的下标 \(pos\)

因为 \(g_{pos}\ge a_i\),所以只能用 \(g_{pos-1}\) 来更新 \(f_i\)。那么 \(f_i=pos-1+1=pos\)

为什么要加一?因为普通的 LIS 转移就是从前边加上一个长度。而前边的长度为 \(pos-1\)。所以答案为 \(pos\)

再来看看如何更新 \(g\) 数组。

因为 \(g_{pos}\ge a_i\),且 \(f_i=pos\)。所以\(g_{pos}\) 更新为 \(a_i\) 一定是不劣的。所以可以直接将 \(g_{pos}\) 更新为 \(a_i\)

最后再更新一波 \(g\) 的长度即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int n,a[N],f[N],g[N],glen,maxn;

int main(){
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;++i)cin>>a[i];
	for(int i=1;i<=n;++i)
	{
		int pos=lower_bound(g+1,g+glen+1,a[i])-g;//找到第一个大于等于a_i的位置
		f[i]=pos;//上述更新
		g[pos]=a[i];//上述更新
		glen=max(glen,pos);//因为有可能 a_i 结尾的LIS长度为 glen+1,所以需更新
	}	
	for(int i=1;i<=n;++i)
		maxn=max(maxn,f[i]);
	cout<<maxn<<'\n';
	return 0;
}

从此,时间复杂度就从 \(O(n^2)\rightarrow O(n\log n)\)

posted @ 2025-10-18 11:58  Atserckcn  阅读(15)  评论(0)    收藏  举报