最长上升子序列(LIS)

一,定义:

子序列:

子序列是可以通过删除零个或多个元素而不改变剩余元素的顺序从给定序列派生出来的序列。

最长严格单调上升子序列:

即子序列满足ai<aj<ak(i<j<k)

最长单调不下降上升子序列

即子序列满足ai<=aj<=ak(i<j<k)

方法一:二分lis数组

  1. 我们可以用一个数组(显然数组内部是递增的)存储到达长度i时其lis各个长度下最小的数字。
    1. 如遍历数组1,2,4,5,3时,如果更新到i=4,有lis[1]=1,lis[2]=2,lis[3]=4,lis[4]=5。
    2. 更新到i=5,有lis[1]=1,lis[2]=2,lis[3]=3,lis[4]=5。即长度为3的最小结尾可以是3.
    3. 更新的时候因为lis数组是递增的,我们可以二分第一个大于当前a[i]的位置,更新它
  2. 缺点是lis数组记录的是到达长度i时当前lis最长值,即每个长度最小结尾。但是他数组本身lis子序列
int a[N];
void mysolve()
{
	int n;
	cin>>n;
	vector<int>lis;//这里求的是严格递增lis
	for(int i=1; i<=n; ++i)
		{
			cin>>a[i];
			if(lis.empty()||a[i]>lis.back())lis.push_back(a[i]);//严格递增你如果要放最后,那你必须>lis数组当前最后一个,如果是单调不下降则是>=
			else lis[lower_bound(lis.begin(),lis.end(),a[i])-lis.begin()]=a[i];//严格递增如果你要替换,那么如果有相等的,必须替换掉,否则会出现=的情况,所以用lower_bound.如果是单调不下降,用upper_bound,即允许=存在
		}
	cout<<lis.size()<<endl;
	for(auto v:lis)cout<<v<<" ";
//	8
//	3 1 2 6 4 5 10 7
//	请按任意键继续. . .
//	1 2 4 5 7
}

方法二:树状数组维护

  1. 思路是将原有数组排序(但是要记录原来的下标),那么我们可以用树状数组维护当前<a[i]的值并提前出现(即下标<a[i]对应下标)的数有多少个
  2. 因为排过序,所以出现在到a[i]时,树状数组存储的下标小于a[i]对应下标的数肯定是小于(等于,看你要的是严格还是不严格递增(注释有说明))a[i]的。
  3. 缺点是码量大(乐)
const int N = 2e5 + 10;
struct node
{
	int first,second;
	bool operator<(const node&k)const
	{
		if(first!=k.first)return first<k.first;
		else return second>k.second;
	}
} a[N];
int t[N],lis[N];
int n;
void add(int x,int mx)
{
	for(int i=x; i<=n; i+=i&-i)t[i]=max(t[i],mx);
}

int ask(int x)
{
	int mx=0;
	for(int i=x; i; i-=i&-i)mx=max(t[i],mx);
	return mx;
}
void mysolve()
{
	cin>>n;
	for(int i=1; i<=n; ++i)cin>>a[i].first,a[i].second=i;
	sort(a+1,a+1+n);//这里求的是严格递增,所以排序需要判位置,即如果元素val相等,需要下标大的排前面,才不会出现=。如果是单调不递减,则是下标小的放前面(sort是不稳定排序,需要明确)。
	for(int i=1; i<=n; ++i)
		{
			lis[a[i].second]=ask(a[i].second)+1;
			add(a[i].second,lis[a[i].second]);
		}
	for(int i=1; i<=n; ++i)cout<<lis[i]<<" ";//lis记录每个下标可以取得的lis
//	8
//	3 1 2 4 5 4 6 4
//	请按任意键继续. . .
//	1 1 2 3 4 3 5 3
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值