【题目描述】
给一个长度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;
}