zoukankan      html  css  js  c++  java
  • 平衡树 Splay

    可以支持的操作:

    1. 插入 xxx 数
    2. 删除 xxx 数(若有多个相同的数,因只删除一个)
    3. 查询 xxx 数的排名(排名定义为比当前数小的数的个数 +1 。若有多个相同的数,因输出最小的排名)
    4. 查询排名为 xxx 的数
    5. xxx 的前驱(前驱定义为小于 xxx ,且最大的数)
    6. xxx 的后继(后继定义为大于 xxx ,且最小的数)

      7.来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1

     0.预备:

    struct有:siz,sum(该点出现次数),fa,val,以及ch[0],ch[1]

    const int N=100000+10;
    const int inf=0x3f3f3f3f;
    int n,m;
    struct node{
        int siz,sum,val,fa;
        int ch[2];
    }t[N];
    int pc,dc,dp[N];
    int root;

    1.pushup,新节点,回收节点

    void pushup(int x){
        t[x].siz=t[t[x].ch[0]].siz+t[t[x].ch[1]].siz+t[x].sum;
    }
    int newnode(int v,int f){
        int r=dc?dp[dc--]:++pc;
        memset(t+r,0,sizeof (node));
        t[r].siz=1,t[r].sum=1,t[r].fa=f,t[r].val=v;
        return r;
    }
    void del(int x){
        dp[++dc]=x;
    }

    2.rotate(x),与父亲旋转。注意每一步的转移,注意最后pushup(y)

    void rotate(int x){
        int y=t[x].fa,d=t[y].ch[1]==x;
        t[t[y].ch[d]=t[x].ch[d^1]].fa=y;
        t[t[x].fa=t[y].fa].ch[t[t[y].fa].ch[1]==y]=x;
        t[x].ch[d^1]=y,t[y].fa=x;
        pushup(y);
    }

    3.splay(x,f),旋转到f的儿子,双旋操作,一次考虑两个节点。注意,无论如何,rotate(x),无论如何,pushup(x),如果f=0,root=x

    void splay(int x,int f){
        while(t[x].fa!=f){
            int y=t[x].fa,z=t[y].fa;
            if(z!=f){
                rotate(((t[z].ch[0]==y)==(t[y].ch[0]==x))?y:x);
            }
            rotate(x);
        }
        pushup(x);
        if(f==0) root=x;
    }

    4.insert(val),注意找到相同的值之后,break掉,注意每次都要splay到根(虽然数据水,不旋更快),理论保证树高。

    void insert(int val){
        if(!root){
            root=newnode(val,0);
            return;
        }
        int u=root;
        while(1){
            t[u].siz++;
            if(t[u].val==val){
            t[u].sum++;
            break;
            }
            int d=t[u].val<val;
            if(!t[u].ch[d]){
                t[u].ch[d]=newnode(val,u);
                u=t[u].ch[d];
                break;
            }
            u=t[u].ch[d];
        }
        //splay(u,0);
    }

    5.dele(val),步骤:先找到val,旋转到根,再找到根节点右子树最小值,即root的后继,splay到根的儿子(必然是右儿子),再删掉root,改root为右儿子

    需要特判:右子树不存在?直接将左二子当做新根。删掉的是最后一个值?我这个代码不怕,root会直接变成0

    void dele(int val){
        int goal=root,son=0;
        while(1){
            if(t[goal].val==val) break;
            goal=t[goal].ch[t[goal].val<val];
        }
        t[goal].sum--;
        splay(goal,0);
        t[goal].siz--;
        if(t[goal].sum>0) return;
        son=t[goal].ch[1];
        while(son&&t[son].ch[0]){
            son=t[son].ch[0];
        }
        if(son){
            splay(son,root);
            t[son].ch[0]=t[root].ch[0];
            t[t[root].ch[0]].fa=son;
            del(root);
            root=son;
            t[son].fa=0;
            pushup(son);
        }
        else{
            root=t[goal].ch[0];
            t[root].fa=0;
            del(goal);
        }
    }

    6.rank(x),同treap

    7.kth(k),同treap

    8.front(x),同treap

    9.back(x),同treap

    int rank(int val){
        int u=root;
        int ret=1;
        while(u){
            if(t[u].val<val) {
                ret+=t[t[u].ch[0]].siz+t[u].sum;
                u=t[u].ch[1];
            }
            else if(t[u].val==val) {ret+=t[t[u].ch[0]].siz;break;}
            else u=t[u].ch[0]; 
        }
        return ret;
    } 
    int kth(int k){
        int u=root;
        while(1){
            if(!u) return 0;
            int d=k-t[t[u].ch[0]].siz;
            if(d<=0) u=t[u].ch[0];
            else if(d>=1&&d<=t[u].sum) return t[u].val;
            else {
                k=d-t[u].sum;
                u=t[u].ch[1];
            }
        }
    }
    int front(int val){
        int u=root;
        int ret=-inf;
        while(u){
            if(t[u].val<val){
                ret=max(ret,t[u].val);
                u=t[u].ch[1];
            }
            else {
                u=t[u].ch[0];
            }
        }
        return ret;
    }
    int back(int val){
        int u=root;
        int ret=inf;
        while(u){
            if(t[u].val>val){
                ret=min(ret,t[u].val);
                u=t[u].ch[0];
            }
            else{
                u=t[u].ch[1];
            }
        }
        return ret;
    }

    10.pushdown(x),翻转其实就是不停地交换节点的两个子树。因为在每次访问到这个节点的时候,尤其在splay的时候,必然会启动pushdown,所以,翻转最终一定会都实现。下放旋转标记,经常勤调用,要记得。

    11.work(l,r),处理旋转操作,这里,为了避免l=1,r=n的情况,放置1,n+2两个哨兵,每个节点的编号实际上是编号减一。最后输出要减一。

    这个方法是针对1~n的排列,如果不是,最好是特判,其实加哨兵也是可以的。

    实现的时候,先找到kth(l)也就是第k大的值,体现在中序遍历里就是这个点的编号。当然,其实找的是l-1,但是由于编号是从2开始的。

    将l旋转到根。

    同理,r=kth(r+2)。

    将r旋转到根节点的儿子(必然是右儿子)

    这样,根节点的右儿子的左子树就是所求区间。

    12.write(o),记得pushdown,输出的编号一定要在[2,n+1]内,因为1,n+2都是哨兵。按照中序遍历输出即可

    对于一个需要维护区间操作的平衡树来说,每个节点的排序方式是编号大小,也就是中序遍历的节点编号一定是一个从1~n的单增序列。保证可以取l-1到根节点,r+1到根节点的儿子,就可以取出[l,r]了。

    注意事项:

    1.rotate里面的压行,不要记错,不行就手写。其实,第一行换儿子,第二行改父亲,第三行改两点之间的关系。

    2.插入元素的时候,如果用while1循环,记得每到一个节点, 那么这个节点的总的siz必然要加一,不论是新赋值,还是多了一个新值。

    3.插入元素的时候,如果找到了这个值,那么就要break,不能继续走了。

    4.删除的时候找右子树中的最大值,为了防止哨兵出了问题,应改写为:

    while(son&&t[son].ch[0]){
            son=t[son].ch[0];
        }

    5.区间反转的时候,kth,write,以及各种访问到这个点的时候,都要pushdown

  • 相关阅读:
    [XJOI]noip43 T2多人背包
    Codeforces Round #198 (Div. 2)E题解
    [XJOI]noip40 T2统计方案
    Codeforces Round #198 (Div. 2)C,D题解
    Codeforces Round #198 (Div. 2)A,B题解
    9.19[XJOI] NOIP训练37
    9.18[XJOI] NOIP训练36
    kmp算法详解
    [模板系列] AC自动姬
    luogu1967[NOIP2013D1T3] 货车运输 (最大生成树+LCA)
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9102198.html
Copyright © 2011-2022 走看看