【胡策】一道题(DP+平衡树)

【题意】给定n个数字ai和L,R,ai=0代表在L~R中任取,求最长上升子序列。n<=10^5,0<=ai<=10^5。

【算法】DP+平衡树(fhq-treap)

【题解】对于LIS,有两种经典O(n^2)的递推式,即

①f[i]表示以 i 结尾的LIS,f[i]=max(f[k]+1),k<i&&a[k]<a[i]。

由于本题的数字范围很小,且L~R与数字范围密切相关,所以采用下一种递推式。

②f[i][j]表示前 i 个数以数字 j 结尾的LIS,f[i][ai]=max(f[i-1][k])+1,k<ai,f[i][j]=f[i-1][j],j≠ai。

进一步地,令f[i][j]表示前 i 个数以<=j的数字结尾的LIS,当ai≠0时有:

Ⅰj<ai,f[i][j]=f[i-1][j]

Ⅱj=ai,f[i][ai]=f[i-1][ai-1]+1

Ⅲj>ai,f[i][j]=max(f[i-1][j],f[i][ai])  本来是f[i][j-1],但实际上只有f[i][ai]改动了。

根据定义,f[i][j],j=1~max(ai)是一个单调不降的序列,这就使ai=0的情况很容易维护(ai≠0视为L=ai=R即可):

Ⅰj<L,f[i][j]=f[i-1][j]

ⅡL<=j<=R,f[i][j]=f[i-1][j-1]+1

Ⅲj>R,f[i][j]=max(f[i-1][j],f[i][R])

省略第一维,第二维的转移用平衡树维护,即:

Ⅰj<L,不变

ⅡL<=j<=R,实际上是从[L-1,R-1]整体平移,只须删除节点R,并在L-1左侧插入原L-1节点,然后对L~R整体+1。

Ⅲj>R,对f[i][R]取max,由于序列单调可以套路化地进行区间覆盖,当然由于只有单点查询也可以直接区间取max。

复杂度O(n log n)。

注意:

1.多标记,一定要注意标记顺序,标记cover时要清除add。

2.t[t[k].l].sz+1

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
using namespace std;
int read(){
    char c;int s=0,t=1;
    while(!isdigit(c=getchar()))if(c=='-')t=-1;
    do{s=s*10+c-'0';}while(isdigit(c=getchar()));
    return s*t;
}
const int maxn=100010;
int tot=0,root=0,n,m,L,R,a[maxn];
struct node{int l,r,rnd,delta,cover,num,sz;}t[maxn];
int newnode(int k,int x){t[k]=(node){0,0,rand(),0,0,x,1};return k;}
void modify(int k,int x){if(!k)return;t[k].delta+=x;t[k].num+=x;}
void Modify(int k,int x){if(!k)return;t[k].cover=x;t[k].num=x;t[k].delta=0;}//more tags
void down(int k){
    if(!k)return;
    if(t[k].cover){
        Modify(t[k].l,t[k].cover);
        Modify(t[k].r,t[k].cover);
        t[k].cover=0;
    }
    if(t[k].delta){
        modify(t[k].l,t[k].delta);
        modify(t[k].r,t[k].delta);
        t[k].delta=0;
    }
}
void up(int k){t[k].sz=1+t[t[k].l].sz+t[t[k].r].sz;}
int merge(int a,int b){
    if(!a||!b)return a^b;
    if(t[a].rnd<t[b].rnd){
        down(a);
        t[a].r=merge(t[a].r,b);
        up(a);
        return a;
    }
    else{
        down(b);
        t[b].l=merge(a,t[b].l);
        up(b);
        return b;
    }
}
void split(int k,int& l,int& r,int x){
    if(!k)return void(l=r=0);
    down(k);
    if(x<t[t[k].l].sz+1){
        r=k;
        split(t[k].l,l,t[k].l,x);
    }
    else{
        l=k;
        split(t[k].r,t[k].r,r,x-t[t[k].l].sz-1);
    }
    up(k);
}
int find(int k,int x){
    if(x==t[t[k].l].sz+1)return t[k].num;
    down(k);
    if(x<t[t[k].l].sz+1)return find(t[k].l,x);//
    else return find(t[k].r,x-t[t[k].l].sz-1);
}
int found(int k,int x){
    if(!k)return 0;
    down(k);
    if(x<t[k].num)return found(t[k].l,x);
    else return t[t[k].l].sz+1+found(t[k].r,x);
}
void work(int l,int r){
    int a,b,c,d,e=l-1==0?0:find(root,l-1);
    split(root,c,d,r);
    split(c,b,c,r-1);
    split(b,a,b,l-2);
    newnode(c,e);
    down(b);modify(b,1);
    root=merge(a,c);
    root=merge(root,b);
    split(d,a,b,found(d,find(root,r)));
    down(a);Modify(a,find(root,r));
    root=merge(root,a);
    root=merge(root,b);
}
int main(){
    srand(183);
    n=read();L=read();R=read();m=R;
    for(int i=1;i<=n;i++)a[i]=read(),m=max(m,a[i]);
    root=0;
    for(int i=0;i<=m;i++)root=merge(root,newnode(++tot,0));
    for(int i=1;i<=n;i++){
        if(a[i])work(a[i]+1,a[i]+1);else work(L+1,R+1);
    }
    printf("%d",find(root,m+1));
    return 0;
}
View Code

 

差分写法:

在递推式的基础上,可以用平衡树维护差分,即平衡树上的每一点代表f[i]-f[i-1],因为相差只有1,所以每个点只有0和1两种属性。

Ⅰj<L,不变

ⅡL<=j<=R,从[L-1,R-1]整体平移时,内部的差分是不变的,那么在L左侧插入一个节点1(区间+1)。

Ⅲj>R,删除>=R的第一个节点1。

 

posted @ 2017-12-23 10:17  ONION_CYC  阅读(318)  评论(0编辑  收藏  举报