zoukankan      html  css  js  c++  java
  • 伸展树(Splay Tree)

    伸展树是一种自平衡二叉查找树,它将每次操作的节点都旋转到根节点,伸展树操作的均摊时间复杂度为logn

    基本操作

    存储

    struct Splay{
        int key,size,num;//如果没有重复元素可不要num,如果无需维护元素排名可不要size 
        Splay *son[2],*fa;
        Splay(){
            memset(this,0,sizeof(Splay));
        }
        Splay(int x){
            key=x,num=size=1,fa=son[0]=son[1]=0;
        }
    }*root;

    伸展操作

      伸展树的最基本的操作当然就是伸展了,这也是它自平衡的基础
      splay(x,S)表示在保持伸展树有序性的前提下,通过一系列旋转将伸展树S中的元素x调整至树的根部。

      旋转操作就不说了

      

      伸展操作大致分为3种情况(左右对称的算一种)

      1. x的父亲节点就是根节点:
        这时我们只需要单次旋转(x如果是左子节点就右旋,是右子节点就左旋)将x旋转到根即可
      2. x的父亲节点和x同为左节点(右节点),如下图中x为4的情况:

        这时我们先将父节点旋转到爷爷节点

        然后再将x旋转到父节点

        这样x就向上移了两层,然后再进行判断并继续上移直到x为根节点

      3. x为右子节点但x父亲为左子节点或x为左子节点但x父亲为右子节点,如下图x为5的情况:

        这时,可以先将x旋转到父节点

        然后将x旋转到之前的爷爷节点

        这样x也向上移了两层,然后再进行判断并继续上移直到x为根节点

      下面是旋转操作和伸展操作的代码(代码中将伸展操作中2,3情况的旋转分开了,也可以将旋转合并成一次操作)

    #define size(x) (x?x->size:0)
    #define is_r(x) (x->fa?x->fa->son[1]==x:0)
    void rotate(Splay *&x,int d){//将x子树旋转,d=0表示左旋,d=1表示右旋 
        Splay *p=x->son[!d];
        x->son[!d]=p->son[d];//更新指针指向关系 
        if(p->son[d])p->son[d]->fa=x;
        p->son[d]=x,p->fa=x->fa,x->fa=p;
        p->size=x->size,x->size=size(x->son[0])+size(x->son[1])+x->num;//更新size值 
        x=p;
    }
    void up(Splay *x){//将x旋转到它的爷爷节点 
        bool d=is_r(x);
        Splay *&a=x->fa->fa->fa?x->fa->fa->fa->son[is_r(x->fa->fa)]:root;//a为指向爷爷节点的指针 
        if(is_r(x->fa)!=d){ 
            rotate(a->son[!d],!d),rotate(a,d);
        }
        else rotate(a,!d),rotate(a,!d);
    }
    void splay(Splay *x,Splay *&root){//伸展操作,将x节点旋转到root节点 
        while(x->fa!=root&&x!=root)up(x);
        if(x->fa==root)rotate(root,!is_r(x));
    }

    插入操作

       插入操作和二叉查找树的相同,只是插入过后执行伸展操作将节点旋转到根节点

    void insert(Splay *&x,Splay *fa,int key){//插入key 
        if(!x){//插入到此 
            x=new Splay(key),x->fa=fa,splay(x,root);
            return;
        }
        x->size++;
        if(x->key==key){x->num++,splay(x,root);return;}//树中已经有key了
        int d=x->key<key;
        insert(x->son[d],x,key);
    }

    合并两棵splay子树

    可合并前提:一棵子树的所有元素都大于另一棵子树的所有元素

      首先将较小的那棵子树的最大的元素旋转到树根,这时它一定没有右子节点,将另一棵子树接上去即可

    Splay* Max(Splay *x){//返回指向x中最大的节点的指针 
        while(x->son[1])x=x->son[1];
        return x;
    }
    Splay* Min(Splay *x){//返回指向x中最小的节点的指针 
        while(x->son[0])x=x->son[0];
        return x;
    }
    void Merge(Splay *&x,Splay *&y){//合并x,y子树(x中所有元素>y中所有元素)
        Splay *a=Max(x);//找到x中最大的节点 
        splay(a,x);//将x中最大的节点旋转到根 
        a->son[1]=y,y->fa=a;//将y接到x的右子节点 
    }

    删除操作

      将要删除的节点旋转到树根,然后合并它的两棵子树即可

    Splay* Find(Splay *x,int key){//返回指向权值为key的节点的指针 
        if(!x)return 0;
        if(x->key==key)return x;
        return Find(x->son[key>x->key],key);
    }
    void del(Splay *&x,int key){//删除 
        Splay *a=Find(x,key);//找到key 
        if(!a)return;//没找到就直接返回 
        splay(a,x);//将a旋转到根 
        if(a->num>1){a->num--,a->size--;return;}//插入不止一次,减一次即可 
        if(!a->son[0]){//左子节点为空,就直接用右子树替换原树 
            if(!a->son[1])root=NULL,delete a;
            else root=a->son[1],a->son[1]->fa=0,delete a;return;
        }
        if(!a->son[1]){//右子节点为空一样 
            root=a->son[0],a->son[0]->fa=0,delete a;return;
        }
        x=a->son[0],a->son[0]->fa=0;//合并两个子树 
        Merge(x,a->son[1]),delete a;
    }

    其他操作

    void mid_traversal(Splay *x){//中序遍历
        if(x->son[0])mid_traversal(x->son[0]);
        printf("%d ",x->key);
        if(x->son[1])mid_traversal(x->son[1]);
    }
    int query_id(Splay *x,int key){//求数列中比key小的有几个 
        if(!x)return 0;
        if(x->key>key)return query_id(x->son[0],key);
        if(x->key==key)return size(x->son[0]);
        return query_id(x->son[1],key)+size(x->son[0])+x->num;
    }
    int query_k(Splay *x,int k){//求排第k的数 
        if(!x)return 0;
        if(size(x->son[0])>=k)return query_k(x->son[0],k);
        if(size(x->son[0])+x->num>=k)return x->key;
        return query_k(x->son[1],k-size(x->son[0])-x->num);
    }
    int ans;
    void pre(Splay *x,int num){//求num的前驱(即小于num的最大的数),并存在ans里 
        if(!x)return;
        if(x->key<num)ans=x->key,pre(x->son[1],num);
        else pre(x->son[0],num);
    }
    void suc(Splay *x,int num){//求后继 
        if(!x)return;
        if(x->key>num)ans=x->key,suc(x->son[0],num);
        else suc(x->son[1],num);
    }

    例题P3369 【模板】普通平衡树(Treap/SBT)

    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define size(x) (x?x->size:0)
    #define is_r(x) (x->fa?x->fa->son[1]==x:0)
    struct Splay{
        int key,size,num;//如果没有重复元素可不要num,如果无需维护元素排名可不要size 
        Splay *son[2],*fa;
        Splay(){
            memset(this,0,sizeof(Splay));
        }
        Splay(int x){
            key=x,num=size=1,fa=son[0]=son[1]=0;
        }
    }*root;
    void mid_traversal(Splay *x){//中序遍历
        if(x->son[0])mid_traversal(x->son[0]);
        printf("%d ",x->key);
        if(x->son[1])mid_traversal(x->son[1]);
    }
    void rotate(Splay *&x,int d){//将x子树旋转,d=0表示左旋,d=1表示右旋 
        Splay *p=x->son[!d];
        x->son[!d]=p->son[d];//更新指针指向关系 
        if(p->son[d])p->son[d]->fa=x;
        p->son[d]=x,p->fa=x->fa,x->fa=p;
        p->size=x->size,x->size=size(x->son[0])+size(x->son[1])+x->num;//更新size值 
        x=p;
    }
    void up(Splay *x){//将x旋转到它的爷爷节点 
        bool d=is_r(x);
        Splay *&a=x->fa->fa->fa?x->fa->fa->fa->son[is_r(x->fa->fa)]:root;//a为指向爷爷节点的指针 
        if(is_r(x->fa)!=d){ 
            rotate(a->son[!d],!d),rotate(a,d);
        }
        else rotate(a,!d),rotate(a,!d);
    }
    void splay(Splay *x,Splay *&root){//伸展操作,将x节点旋转到root节点 
        while(x->fa!=root&&x!=root)up(x);
        if(x->fa==root)rotate(root,!is_r(x));
    }
    void insert(Splay *&x,Splay *fa,int key){//插入key 
        if(!x){//插入到此 
            x=new Splay(key),x->fa=fa,splay(x,root);
            return;
        }
        x->size++;
        if(x->key==key){x->num++,splay(x,root);return;}//树中已经有key了
        int d=x->key<key;
        insert(x->son[d],x,key);
    }
    Splay* Max(Splay *x){//返回指向x中最大的节点的指针 
        while(x->son[1])x=x->son[1];
        return x;
    }
    Splay* Min(Splay *x){//返回指向x中最小的节点的指针 
        while(x->son[0])x=x->son[0];
        return x;
    }
    void Merge(Splay *&x,Splay *&y){//合并x,y子树(x中所有元素>y中所有元素)
        Splay *a=Max(x);//找到x中最大的节点 
        splay(a,x);//将x中最大的节点旋转到根 
        a->son[1]=y,y->fa=a;//将y接到x的右子节点 
    }
    Splay* Find(Splay *x,int key){//返回指向权值为key的节点的指针 
        if(!x)return 0;
        if(x->key==key)return x;
        return Find(x->son[key>x->key],key);
    }
    void del(Splay *&x,int key){//删除 
        Splay *a=Find(x,key);//找到key 
        if(!a)return;//没找到就直接返回 
        splay(a,x);//将a旋转到根 
        if(a->num>1){a->num--;return;}//插入不止一次,减一次即可 
        if(!a->son[0]){//左子节点为空,就直接用右子树替换原树 
            root=a->son[1],a->son[1]->fa=0,delete a;return;
        }
        if(!a->son[1]){//右子节点为空一样 
            root=a->son[0],a->son[0]->fa=0,delete a;return;
        }
        x=a->son[0],a->son[0]->fa=0;//合并两个子树 
        Merge(x,a->son[1]),delete a;
    }
    int query_id(Splay *x,int key){//求数列中比key小的有几个 
        if(!x)return 0;
        if(x->key>key)return query_id(x->son[0],key);
        if(x->key==key)return size(x->son[0]);
        return query_id(x->son[1],key)+size(x->son[0])+x->num;
    }
    int query_k(Splay *x,int k){//求排第k的数 
        if(!x)return 0;
        if(size(x->son[0])>=k)return query_k(x->son[0],k);
        if(size(x->son[0])+x->num>=k)return x->key;
        return query_k(x->son[1],k-size(x->son[0])-x->num);
    }
    int ans;
    void pre(Splay *x,int num){//求num的前驱(即小于num的最大的数),并存在ans里 
        if(!x)return;
        if(x->key<num)ans=x->key,pre(x->son[1],num);
        else pre(x->son[0],num);
    }
    void suc(Splay *x,int num){//求后继 
        if(!x)return;
        if(x->key>num)ans=x->key,suc(x->son[0],num);
        else suc(x->son[1],num);
    }
    int main(){
        int n,x,y;scanf("%d",&n);
        while(n--){
            scanf("%d%d",&x,&y);
            switch(x){
                case 1:
                    insert(root,0,y);
                    break;
                case 2:
                    del(root,y);
                    break;
                case 3:
                    printf("%d
    ",query_id(root,y)+1);
                    break;
                case 4:
                    printf("%d
    ",query_k(root,y));
                    break;
                case 5:
                    pre(root,y);printf("%d
    ",ans);
                    break;
                default:
                    suc(root,y);printf("%d
    ",ans);
                
            }
        }
        return 0;
    }

    维护非有序序列的区间操作

    实际上Splay Tree不只能维护有序序列,我们可以丢掉它二叉搜索树的性质,然后就可以维护非有序序列

    用处:由于Splay Tree独特的伸展操作,使得它可以较快的实现对区间的操作

    例题SPOJ Another Sequence Problem

    存储

      因为有区间操作,所以每个节点还要存标记

    struct Splay{
        int key,size,maxl,maxr,maxt,sum;//maxl为左区间最大值,maxr为右区间最大值, maxt为区间最大值 
        bool rev,same;//翻转和赋值标记 
        Splay *son[2],*fa;
        Splay(){
            memset(this,0,sizeof(Splay));
        }
        Splay(int x,Splay *f){
            sum=maxl=maxr=maxt=key=x,size=1,rev=same=0,fa=f;
        }
    }

    建树

      每个根节点保存子树区间的中点,然后让左右子树分别保存被中点分成的两个区间

    void build(Splay *&x,Splay *fa,int l,int r){//建立一棵[l,r]的子树,x指向子树的根,根的父亲为fa 
        if(l>r)return;
        int mid=(l+r)>>1;
        x=new Splay(a[mid],fa),x->son[0]=x->son[1]=null;//根节点存区间中点 
        build(x->son[0],x,l,mid-1);
        build(x->son[1],x,mid+1,r);
        update(x);
    }

    更新和下放标记

    int Max(int n,...){//求n个数的max 
        va_list arg_ptr;
        va_start(arg_ptr, n);
        int ans = -inf;
        for(int i=0; i < n; ++i){
            int temp = va_arg(arg_ptr,int);
            ans=max(ans,temp);
        }
        va_end(arg_ptr);
        return ans;
    }
    void push_down(Splay *x){//下放标记 
        if(x==null)return;
        if(x->rev){//区间翻转 
            x->rev=0,x->son[0]->rev^=1,x->son[1]->rev^=1;
            swap(x->son[0],x->son[1]),swap(x->maxl,x->maxr);
        }
        if(x->same){//区间赋值 
            x->same=0,x->son[0]->key=x->son[1]->key=x->key;
            x->son[0]->same=x->son[1]->same=1;
            x->sum=x->key*x->size;
            x->maxl=x->maxr=x->maxt=(x->key>0?x->sum:x->key);
        }
    }
    void update(Splay *x){//更新节点 
        if(x==null)return;
        push_down(x),push_down(x->son[0]),push_down(x->son[1]);//要用到子节点的maxl,maxr所以要下放子节点标记 
        x->size=x->son[0]->size+x->son[1]->size+1,//更新节点存储的值 
        x->sum=x->son[0]->sum+x->son[1]->sum+x->key,
        x->maxl=max(x->son[0]->maxl,x->son[0]->sum+x->key+max(0,x->son[1]->maxl)),
        x->maxr=max(x->son[1]->maxr,x->son[1]->sum+x->key+max(0,x->son[0]->maxr));
        x->maxt=Max(3,x->son[0]->maxt,x->son[1]->maxt,max(0,x->son[0]->maxr)+max(0,x->son[1]->maxl)+x->key);
    }

    旋转和伸展

      和上面的Splay相同,但旋转之前要先下放标记

    void rotate(Splay *&x,int d){//旋转 
        Splay *p=x->son[!d];
        push_down(x),push_down(p);//先下放标记 
        x->son[!d]=p->son[d];//更新指向关系 
        if(p->son[d]!=null)p->son[d]->fa=x;
        p->son[d]=x,p->fa=x->fa,x->fa=p;
        update(x),update(p);//更新节点 
        x=p;//x指向p指向的位置
    }
    void up(Splay *x){//将x旋转到它的爷爷节点 
        bool d=is_r(x);
        Splay *&a=x->fa->fa->fa?x->fa->fa->fa->son[is_r(x->fa->fa)]:root;//a为指向爷爷节点的指针 
        if(is_r(x->fa)!=d){//x和父亲位于它们父亲的异侧 
            rotate(a->son[!d],!d),rotate(a,d);
        }
        else rotate(a,!d),rotate(a,!d);//x和父亲位于它们父亲的同侧 
    }
    void splay(Splay *x,Splay *&root){//将x节点旋转到root节点 
        if(x==null)return;
        while(x->fa!=root&&x!=root){
            up(x);
        }
        if(x->fa==root)rotate(root,!is_r(x));
    }

    查找

      查找分3种情况:

    1. 如果左子树大小$ge$k,则去左子树中查找第k个节点
    2. 如果左子树大小+1=k,则返回当前节点
    3. 否则去右子树中查找第k-(x->son[0]->size+1)个节点
    Splay *Find(Splay *x,int k){//返回x子树中的第k个数 
        if(x==null)return 0;
        update(x);
        if(x->son[0]->size>=k)return Find(x->son[0],k);
        if(x->son[0]->size+1==k)return x;
        return Find(x->son[1],k-x->son[0]->size-1);
    }

    删除

    删除[l,r]区间:

    1. 将原树中的第l-1个数旋转到根
    2. 将原树中的第r+1个数旋转到根的右子节点
    3. 这时根的右子节点的左子树即为要删除的区间

    因为l,r可能是总区间的端点,l-1,r+1会越界,所以我们在区间的两端建两个空节点
    实际操作为第l个数旋转到根,将第r+2个数旋转到根的右子节点(整体右移一个节点),下面的操作相同

    void remove(Splay *x){//删除x子树 
        if(x==null)return;
        remove(x->son[0]),remove(x->son[1]),delete x;
    }
    void del(int l,int r){//删除[l,r] 
        splay(Find(root,l),root);//将l-1旋转到root 
        splay(Find(root,r+2),root->son[1]);//将r+1旋转到root的右儿子
        remove(root->son[1]->son[0]),root->son[1]->son[0]=null,update(root->son[1]),update(root);//r+1的左儿子即为区间[l,r]
    }

    插入

    在x后面插入y个数:

    1. 将原树中的第x个数旋转到根
    2. 将原树中的第x+1个数旋转到根的右子节点
    3. 建一棵Splay保存这y个数
    4. 将新建的树接到根的右子节点的左子节点上
      void insert(int l,int s){//在l后面插入长为s的区间 
          for(int i=0;i<s;i++){
              scanf("%d",a+i);
          }
          splay(Find(root,l+1),root),splay(Find(root,l+2),root->son[1]);//将l旋转到root,将l+1旋转到root的右儿子,这时l+1没有左儿子 
          build(root->son[1]->son[0],root->son[1],0,s-1),update(root),update(root->son[1]);//建树,更新 
      }

    其他操作

    基本所有的对区间的操作都有两个相同的步骤:

    1. 将原树中的第l-1个数旋转到根
    2. 将原树中的第r+1个数旋转到根的右子节点

    这样根的右子节点的左子树即为[l,r],直接操作即可

    void modify(int l,int r,int s){//将[l,r]都赋值为s 
        splay(Find(root,l),root);//将l-1旋转到root 
        splay(Find(root,r+2),root->son[1]);//将r+1旋转到root的右儿子 
        Splay *x=root->son[1]->son[0];//r+1的左儿子即为区间[l,r],打标记更新 
        x->same=1,x->key=s,update(root->son[1]),update(root);splay(x,root);
    }
    void rev(int l,int r){//将[l,r]翻转 
        splay(Find(root,l),root);//将l-1旋转到root 
        splay(Find(root,r+2),root->son[1]);//将r+1旋转到root的右儿子
        root->son[1]->son[0]->rev=1;//r+1的左儿子即为区间[l,r],打标记更新
        update(root->son[1]),update(root);
    }
    int get_sum(int l,int r){//求[l,r]的和 
        splay(Find(root,l),root);
        splay(Find(root,r+2),root->son[1]);
        return root->son[1]->son[0]->sum; 
    }
    int max_sum(){//求最大的区间值 
        update(root); 
        return root->maxt;
    }

    完整代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<stdarg.h>
    using namespace std;
    #define inf 0x3fffffff
    #define is_r(x) (x->fa?x->fa->son[1]==x:0)
    struct Splay{
        int key,size,maxl,maxr,maxt,sum;//maxl为左区间最大值,maxr为右区间最大值, maxt为区间最大值 
        bool rev,same;//翻转和赋值标记 
        Splay *son[2],*fa;
        Splay(){
            memset(this,0,sizeof(Splay));
        }
        Splay(int x,Splay *f){
            sum=maxl=maxr=maxt=key=x,size=1,rev=same=0,fa=f;
        }
    }*root,*null=new Splay;
    int a[500005];
    //下面的宏在<stdarg.h>提供的有 
    //typedef char *  va_list;//指针 
    //#define _ADDRESSOF(n)   ( &reinterpret_cast<const char &>(n) ) //取n的地址并转为char* 
    //#define _INTSIZEOF(n)  ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //将sizeof(n)向上取至sizeof(int)的整数倍,在我电脑上必须*2才行 
    //#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //求第一位的地址存在ap中 
    //#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //取出ap的值,并将ap指向下一个位置
    //#define va_end(ap)      ( ap = (va_list)0 )  //置空ap 
    int Max(int n,...){//求n个数的max 
        va_list arg_ptr;
        va_start(arg_ptr, n);
        int ans = -inf;
        for(int i=0; i < n; ++i){
            int temp = va_arg(arg_ptr,int);
            ans=max(ans,temp);
        }
        va_end(arg_ptr);
        return ans;
    }
    void push_down(Splay *x){//下放标记 
        if(x==null)return;
        if(x->rev){//区间翻转 
            x->rev=0,x->son[0]->rev^=1,x->son[1]->rev^=1;
            swap(x->son[0],x->son[1]),swap(x->maxl,x->maxr);
        }
        if(x->same){//区间赋值 
            x->same=0,x->son[0]->key=x->son[1]->key=x->key;
            x->son[0]->same=x->son[1]->same=1;
            x->sum=x->key*x->size;
            x->maxl=x->maxr=x->maxt=(x->key>0?x->sum:x->key);
        }
    }
    void update(Splay *x){//更新节点 
        if(x==null)return;
        push_down(x),push_down(x->son[0]),push_down(x->son[1]);//要用到子节点的maxl,maxr所以要下放子节点标记 
        x->size=x->son[0]->size+x->son[1]->size+1,//更新节点存储的值 
        x->sum=x->son[0]->sum+x->son[1]->sum+x->key,
        x->maxl=max(x->son[0]->maxl,x->son[0]->sum+x->key+max(0,x->son[1]->maxl)),
        x->maxr=max(x->son[1]->maxr,x->son[1]->sum+x->key+max(0,x->son[0]->maxr));
        x->maxt=Max(3,x->son[0]->maxt,x->son[1]->maxt,max(0,x->son[0]->maxr)+max(0,x->son[1]->maxl)+x->key);
    }
    void mid_traversal(Splay *x){//中序遍历
        push_down(x);
        if(x->son[0]!=null)mid_traversal(x->son[0]);
        printf("%d %d
    ",x->key,x->size);
        if(x->son[1]!=null)mid_traversal(x->son[1]);
    }
    void rotate(Splay *&x,int d){//旋转 
        Splay *p=x->son[!d];
        push_down(x),push_down(p);//先下放标记 
        x->son[!d]=p->son[d];//更新指向关系 
        if(p->son[d]!=null)p->son[d]->fa=x;
        p->son[d]=x,p->fa=x->fa,x->fa=p;
        update(x),update(p);//更新节点 
        x=p;//x指向p指向的位置
    }
    void up(Splay *x){//将x旋转到它的爷爷节点 
        bool d=is_r(x);
        Splay *&a=x->fa->fa->fa?x->fa->fa->fa->son[is_r(x->fa->fa)]:root;//a为指向爷爷节点的指针 
        if(is_r(x->fa)!=d){//x和父亲位于它们父亲的异侧 
            rotate(a->son[!d],!d),rotate(a,d);
        }
        else rotate(a,!d),rotate(a,!d);//x和父亲位于它们父亲的同侧 
    }
    void splay(Splay *x,Splay *&root){//将x节点旋转到root节点 
        if(x==null)return;
        while(x->fa!=root&&x!=root){
            up(x);
        }
        if(x->fa==root)rotate(root,!is_r(x));
    }
    void build(Splay *&x,Splay *fa,int l,int r){//建立一棵[l,r]的子树,x指向子树的根,根的父亲为fa 
        if(l>r)return;
        int mid=(l+r)>>1;
        x=new Splay(a[mid],fa),x->son[0]=x->son[1]=null;//根节点存区间中点 
        build(x->son[0],x,l,mid-1);
        build(x->son[1],x,mid+1,r);
        update(x);
    }
    Splay *Find(Splay *x,int k){//返回x子树中的第k个数 
        if(x==null)return 0;
        update(x);
        if(x->son[0]->size>=k)return Find(x->son[0],k);
        if(x->son[0]->size+1==k)return x;
        return Find(x->son[1],k-x->son[0]->size-1);
    }
    void insert(int l,int s){//在l后面插入长为s的区间 
        for(int i=0;i<s;i++){
            scanf("%d",a+i);
        }
        splay(Find(root,l+1),root),splay(Find(root,l+2),root->son[1]);//将l旋转到root,将l+1旋转到root的右儿子,这时l+1没有左儿子 
        build(root->son[1]->son[0],root->son[1],0,s-1),update(root),update(root->son[1]);//建树,更新 
    }
    void remove(Splay *x){//删除x子树 
        if(x==null)return;
        remove(x->son[0]),remove(x->son[1]),delete x;
    }
    void del(int l,int r){//删除[l,r] 
        splay(Find(root,l),root);//将l-1旋转到root 
        splay(Find(root,r+2),root->son[1]);//将r+1旋转到root的右儿子
        remove(root->son[1]->son[0]),root->son[1]->son[0]=null,update(root->son[1]),update(root);//r+1的左儿子即为区间[l,r]
    }
    void modify(int l,int r,int s){//将[l,r]都赋值为s 
        splay(Find(root,l),root);//将l-1旋转到root 
        splay(Find(root,r+2),root->son[1]);//将r+1旋转到root的右儿子 
        Splay *x=root->son[1]->son[0];//r+1的左儿子即为区间[l,r],打标记更新 
        x->same=1,x->key=s,update(root->son[1]),update(root);splay(x,root);
    }
    void rev(int l,int r){//将[l,r]翻转 
        splay(Find(root,l),root);//将l-1旋转到root 
        splay(Find(root,r+2),root->son[1]);//将r+1旋转到root的右儿子
        root->son[1]->son[0]->rev=1;//r+1的左儿子即为区间[l,r],打标记更新
        update(root->son[1]),update(root);
    }
    int get_sum(int l,int r){//求[l,r]的和 
        splay(Find(root,l),root);
        splay(Find(root,r+2),root->son[1]);
        return root->son[1]->son[0]->sum; 
    }
    int max_sum(){//求最大的区间值 
        update(root); 
        return root->maxt;
    }
    void work(){
        int n,m,l,r,k;scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",a+i);
        }
        a[0]=a[n+1]=-1000000000;
        build(root,0,0,n+1);
        char op[20];
        while(m--){
            scanf("%s",op);
            if(!strcmp(op,"MAKE-SAME")){
                scanf("%d%d%d",&l,&r,&k);
                modify(l,l+r-1,k);
            }
            else if(!strcmp(op,"INSERT")){
                scanf("%d%d",&l,&k);
                insert(l,k);
            }
            else if(!strcmp(op,"DELETE")){
                scanf("%d%d",&l,&r);
                del(l,l+r-1);
            }
            else if(!strcmp(op,"REVERSE")){
                scanf("%d%d",&l,&r);
                rev(l,l+r-1);
            }
            else if(!strcmp(op,"GET-SUM")){
                scanf("%d%d",&l,&r);
                printf("%d
    ",get_sum(l,l+r-1));
            }
            else if(!strcmp(op,"MAX-SUM")){
                printf("%d
    ",max_sum());
            }
        }
        remove(root);
    }
    int main(){
        null->maxt=null->maxl=null->maxr=-inf; 
        int t;scanf("%d",&t); 
        while(t--)work();
    }
  • 相关阅读:
    bootstrap:按钮,下拉菜单
    js:indexOf()方法
    js:object.offsetHeight属性
    css y轴溢出滚动条,x轴溢出显示
    datatables使用方式
    ajax同步与异步的坑
    IntelliJ IDEA 快捷键说明大全(中英对照、带图示详解) (转载)
    无法解析此远程名称: 'www.***.com' 解决办法 请求因 HTTP 状态 417 失败 (转载)
    Windows下安装MongoDB3.6.5 (转载)
    解决内部存储空间紧张,不加载桌面壁纸,桌面壁纸显示
  • 原文地址:https://www.cnblogs.com/bennettz/p/8387220.html
Copyright © 2011-2022 走看看