【基础动态规划】上升序列

【题目描述】
给一个长度10^5的非负序列,序列中的0可以任意换成任何数字(包括负数),问最长严格上升子序列长度。
【输入描述】
第一行有一个数n代表序列长度
第二行有n个数字ai代表序列每个值是多少。
【输出描述】
一行一个数字代表答案。
【样例输入】
7
2 0 2 1 2 0 5
【样例输出】
5

【思路】

首先对于一般的上升序列,一般是利用下面这个dp方程进行转移:
d p [ i ] = m a x ( d p [ j ] ) + 1 ( j < i ) ( a [ j ] < a [ i ] ) dp[i]=max(dp[j])+1(j<i)(a[j]<a[i]) dp[i]=max(dp[j])+1(j<i)(a[j]<a[i])
显然,对于这道题的数据规模是显然过不去的,因此我们希望尝试对这个更新方式进行一些优化。我们注意到,如果dp[i]==dp[j]且a[i]<a[j]时,对于dp[j]而言,显然可以舍弃,因为dp[i]总是更优的。然而对于上面这个方程,却重复扫描大量无用的状态,导致耗时很高,因此我们可以只保留可能的最优解来进行后续更新。于是,我们可以利用f[i]来表示上升序列长度为i的最小a[j]值,即:
f [ i ] = m i n ( a [ j ] ) ( d p [ j ] = = i ) f[i]=min(a[j])(dp[j]==i) f[i]=min(a[j])(dp[j]==i)
由此,我们惊喜的发现,这样所构成的f数组是一定单调不减的,于是很显然 我们可以使用二分查找,因为对于一个数a[i]来说,只需要找到一个恰好大于a[i]的f[j],然后用a[i]尝试去更新f[j]。时间复杂度O(nlogn),而对于这道题本身而言,对于0的处理可以暴力更新所有的f[i],也可以用一个标记变量来标记。
那么这道题就愉快地 A 了。
update:应某人吐槽,我还是加上正解的做法。如果不要求严格上升,求出lis再加上0的个数即可,但是要求严格上升所以我们可以把每个数减去在它前面的0的个数,再来求最长上升子序列,再加上0的数量即可。相当于强制所有0都有贡献,由于强制一个0出现最多导致一个可以出现在lis里的数不出现,所以并不会影响lis的长度。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<string>
#include<vector>
#define re register
using namespace std;
int n,m,a[200010],b,c,t;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int red(){
    char ch=nc();int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
int f[200001];
int main()
{
	memset(f,127/3,sizeof(f));
 	n=red();
 	for(int re i=1;i<=n;i++)
	{
	 	a[i]=red();
	}
	f[0]=-100000;
	if(!a[1])a[1]=-99999;
	f[1]=a[1];
	int mxlen=1;
	for(int re i=2;i<=n;i++)
	{
		if(a[i])
		{
			int l=0,j=0,r=mxlen,mid;
			while(l+1<r)
			{
				mid=(l+r)>>1;
				if(a[i]<=f[mid])r=mid;
				else l=mid;
			}
			j=(a[i]>f[r]?r:l);
			f[j+1]=min(f[j+1],a[i]);
			if(j+1>mxlen)mxlen=j+1;
		}
		else
		{
			for(int re j=mxlen;j>=0;j--)
			{
				f[j+1]=min(f[j+1],f[j]+1);
			}
			mxlen++;
		}
		
	}
	cout<<mxlen;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值