传送门:hdu 5773 The All-purpose Zero
题意:给你n个数(n<=1e5),每个数小于等于1000000,其中数为0的那些数可以变成其他任意的数,问变化之后的最长上升子序列。
思路一:
首先猜想一个结论:那些为0的数一定全部在最长上升子序列中
反证法:假设至少一个0不在最长上升子序列中,那么将这个0的后面一个位置的数替换为0,最长上升子序列的长度一定不变。
那么我们可以想到一个做法,每遇到一个0,就将其后面的数全部减一,表示这个0必取,那么剩下的数的最长上升子序列+0的个数就是答案。
#include<bits/stdc++.h>
using namespace std;
const int maxn=101000;
int a[maxn],b[maxn];
int main(){
int _,n;
scanf("%d",&_);
for(int case1=1;case1<=_;case1++){
scanf("%d",&n);
int pre=0,cnt=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]==0)
pre++;
else
a[i]-=pre,a[++cnt]=a[i];
}
int len=0,pos;
if(cnt>=1){
b[1]=a[1];
len=1;
for(int i=2;i<=cnt;i++){
if(b[len]<a[i])
len++,b[len]=a[i];
else
pos=lower_bound(b+1,b+len+1,a[i])-b,b[pos]=a[i];
}
}
printf("Case #%d: %d\n",case1,len+pre);
}
return 0;
}
思路二:
dp[i]是表示上升子序列长度为i的最小值
如果有一个0要插入,那就dp[i]+1->dp[i+1],然后dp[1]=负无穷
->序列右移->线段树上预留出很多位置,起点左移
#include<bits/stdc++.h>
using namespace std;
const int maxn=200100;
const int inf=0x3f3f3f3f;
int lazy[maxn<<2],maxv[maxn<<2],a[maxn];
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
int L,R,Midpoint,tot,v; //Midpoint表示刚开始的起点的位置
void pushup(int rt){
maxv[rt]=max(maxv[rt<<1],maxv[rt<<1|1]);
}
void build(int l,int r,int rt){
lazy[rt]=0;
if(l==r){
if(l<Midpoint)
maxv[rt]=-inf;
else
maxv[rt]=inf;
return ;
}
int m=(l+r)>>1;
build(lson);
build(rson);
pushup(rt);
}
void pushdown(int rt){
maxv[rt<<1]+=lazy[rt],maxv[rt<<1|1]+=lazy[rt];
lazy[rt<<1]+=lazy[rt],lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
void update(int l,int r,int rt){
if(l==r){
maxv[rt]=v;
return ;
}
pushdown(rt);
int m=(l+r)>>1;
if(L<=m&&maxv[rt<<1]>=v)
update(lson);
else
update(rson);
pushup(rt);
}
int query(int l,int r,int rt){
if(L<=l&&R>=r)
return maxv[rt];
int m=(l+r)>>1;
if(L<=m)
return query(lson);
else
return query(rson);
}
int main(){
int _,n;
scanf("%d",&_);
for(int case1=1;case1<=_;case1++){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
Midpoint=n+5,tot=n*2+10;
build(1,tot,1);
for(int i=1;i<=n;i++){
if(a[i]==0){
++lazy[1];
L=--Midpoint,R=L,v=-inf;
update(1,tot,1);
}
else
v=a[i],L=n+5,R=tot,update(1,tot,1);
}
int ans=0;
for(int i=Midpoint;i<=tot;i++){
L=i,R=i;
if(query(1,tot,1)>=inf)
break;
ans=i-Midpoint+1;
}
printf("Case #%d: %d\n",case1,ans);
}
return 0;
}
思路三:
cdq+扫描线+树状数组
假设我们现在要求len[x]=max( len[i]+min(a[x]-a[i]-1,pre[x]-pre[i]) )+1
假设a[x]>a[i],我们对min(a[x]-a[i]-1,pre[x]-pre[i])进行讨论,维护一个三维偏序
1.a[x]-a[i]-1<=pre[x]-pre[i]-> a[x]-pre[x]-1<=a[i]-pre[i],这时候加上的最大值便为len[i]+(a[x]-a[i]-1)
2.a[x]-a[i]-1>pre[x]-pre[i]-> a[x]-pre[x]-1>a[i]-pre[i],这时候加上的最大值便为len[i]+(pre[x]-pre[i])
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=200100;
int m,pre[maxn],a[maxn],B[maxn],b[maxn],ans,T,len[maxn];
int qa[maxn],qb[maxn],pos0[maxn],pos1[maxn],maxv0[maxn],maxv1[maxn],tot,num,C[maxn];
bool cmp(int x,int y){
return a[x]<a[y];
}
void add0(int x,int y){
for(int i=x;i>0;i-=(i&-i)){
if(pos0[i]!=T)
maxv0[i]=y,pos0[i]=T;
else
maxv0[i]=max(maxv0[i],y);
}
}
void add1(int x,int y){
for(int i=x;i<=num;i+=(i&-i)){
if(pos1[i]!=T)
maxv1[i]=y,pos1[i]=T;
else
maxv1[i]=max(maxv1[i],y);
}
}
void ask0(int &ans,int y,int x){
while(x<=num){
if(pos0[x]==T)
ans=max(ans,y+maxv0[x]);
x+=(x&-x);
}
}
void ask1(int &ans,int y,int x){
while(x>0){
if(pos1[x]==T)
ans=max(ans,y+maxv1[x]);
x-=(x&-x);
}
}
void solve(int l,int r){
if(l==r){
len[l]++;
return ;
}
int mid=l+r>>1;
solve(l,mid);
int Count_a=0,Count_b=0;
for(int i=l;i<=mid;i++)
qa[Count_a++]=i;
for(int i=mid+1;i<=r;i++)
qb[Count_b++]=i;
sort(qa,qa+Count_a,cmp),sort(qb,qb+Count_b,cmp);
int id=0,i;
for(T++,i=0;i<Count_b;i++){
while(id<Count_a&&a[qa[id]]<a[qb[i]]){
add0(b[qa[id]],len[qa[id]]-a[qa[id]]-1);//比b[qa[id]]小的位置加上len[qa[id]]-a[qa[id]]-1
add1(b[qa[id]],len[qa[id]]-pre[qa[id]]);//相反
id++;
}
int num1=lower_bound(B+1,B+num+1,a[qb[i]]-pre[qb[i]]-1)-B;
ask0(len[qb[i]],a[qb[i]],num1);
ask1(len[qb[i]],pre[qb[i]],num1-1);
}
solve(mid+1,r);
}
int main(){
int _,n;
scanf("%d",&_);
for(int case1=1;case1<=_;case1++){
scanf("%d",&n);
a[m=1]=-inf,T=0;
int Count=0,x;
memset(len,0,sizeof(len));
memset(pre,0,sizeof(pre));
memset(pos0,0,sizeof(pos0));
memset(pos1,0,sizeof(pos1));
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(x==0)
Count++;
else
pre[++m]=Count,a[m]=x;
}
pre[++m]=Count,a[m]=inf,tot=0;
for(int i=1;i<=m;i++)
B[++tot]=b[i]=a[i]-pre[i],B[++tot]=b[i]-1;
sort(B+1,B+tot+1);
num=unique(B+1,B+tot+1)-B-1;
for(int i=1;i<=m;i++)
b[i]=lower_bound(B+1,B+num+1,b[i])-B;
solve(1,m);
printf("Case #%d: %d\n",case1,len[m]-2);
}
return 0;
}