zoukankan      html  css  js  c++  java
  • Splay平衡树入门小结

    学习到这部分算是数据结构比较难的部分了,平衡树不好理解代码量大,但在某些情况下确实是不可替代的,所以还是非学不可。

    建议先学Treap之后在学Splay,因为其实Splay有不少操作和Treap差不多的。学习了Treap和Splay之后也可以比较两者的差别以及各自的优劣势。

    推荐博客:https://www.cnblogs.com/zwfymqz/p/7896036.html  这个大佬说得很好了,认真看了应该就能学会了。

    还有https://blog.csdn.net/chenxiaoran666/article/details/81414567

    这里讲一下Splay的优势或者说特点,Splay的特别函数是splay操作即伸展操作,其实伸展操作也是基于旋转操作的,它的作用就是把某个点x通过双旋旋转到树根root且没有破坏平衡树的结构,因为双旋的特点可以被证明(这是tarjan大神证明的)这样会使得Splay平衡树的各个操作均摊时间复杂度为O(logn)。只要会用平衡树,其实不用深入了解它的证明过程(只是蒟蒻的观点,毕竟这个很难理解且似乎对做题没什么帮助),但是Splay各个操作函数的原理以及对树带来的影响还是得理解的,这样也方便我们对它进行改造。

    首先是模板题:洛谷P3369 普通平衡树

    代码几乎是抄上诉博客大佬的,但是大佬的原代码过不了第12个数据,似乎是Rank这个函数没有做Splay操作(不知道是不是这个原因,至少我加了Splay操作就对了)。

    #include<bits/stdc++.h>
    #define root T[0].ch[1]
    using namespace std;
    const int N=1e5+10;
    const int INF=0x3f3f3f3f;
    int n,m;
    struct node{
        int fa,ch[2],val,rec,sum;  //recÊǸõã´óС£¬sumÊǸõã×ÓÊ÷´óС 
    }T[N];
    int tot=0;
    void update(int x) { T[x].sum=T[T[x].ch[0]].sum+T[T[x].ch[1]].sum+T[x].rec; }
    int ident(int x) { return T[T[x].fa].ch[0]==x?0:1; }  //·µ»ØxÊÇfaµÄÄĸö¶ù×Ó 
    void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; }  //x½Úµã½«³ÉΪfa½ÚµãµÄhowº¢×Ó
    void rotate(int x) {  //°ÑxÍùÉÏÐýת(°üÀ¨×óÓÒÐý) 
        int Y=T[x].fa,R=T[Y].fa;
        int Yson=ident(x),Rson=ident(Y); 
        connect(T[x].ch[Yson^1],Y,Yson);
        connect(Y,x,Yson^1);
        connect(x,R,Rson);
        update(Y); update(x);
    }
    void splay(int x,int to) {  //ÉìÕ¹²Ù×÷£º°ÑxË«Ðýµ½to 
        to=T[to].fa;
        while (T[x].fa!=to) {
            int y=T[x].fa;
            if (T[y].fa==to) rotate(x);
            else if (ident(x)==ident(y)) rotate(y),rotate(x);
            else rotate(x),rotate(x);
        }
    }
    int newnode(int v,int f) {  //н¨½áµãֵΪv£¬faΪf 
        T[++tot].fa=f;
        T[tot].rec=T[tot].sum=1;
        T[tot].val=v;
        return tot;
    }
    void Insert(int x) {  //²åÈëֵΪxµÄ½áµã 
        int now=root;
        if (root==0) newnode(x,0),root=tot;
        else {
            while (1) {
                T[now].sum++;
                if (T[now].val==x) { T[now].rec++; splay(now,root); return; }
                int nxt=x<T[now].val?0:1;  //¸ù¾ÝÖµµÄ´óС¼ÌÐøÍùϲåÈë 
                if (!T[now].ch[nxt]) {  //·¢ÏÖÊǿյ㣬´´½¨Ðµã 
                    int p=newnode(x,now);
                    T[now].ch[nxt]=p;
                    splay(p,root); return;
                }
                now=T[now].ch[nxt];  //·ñÔò¼ÌÐøÍùϲåÈë 
            }
        }
    }
    int find(int x) {  //²éÕÒֵΪxµÄλÖò¢·µ»Ø 
        int now=root;
        while (1) {
            if (!now) return 0;  //ûÓÐÕâ¸öµã 
            if (T[now].val==x) { splay(now,root); return now; }  //ÕÒµ½ÁË 
            int nxt=x<T[now].val?0:1;  //¸ù¾ÝÖµ¼ÌÐøÍùÏÂÕÒ 
            now=T[now].ch[nxt];
        }
    }
    void Delete(int x) {  //ɾ³ýֵΪxµÄµã 
        int pos=find(x);  //ÏÈÕÒµ½Õâ¸öµãµÄλÖà 
        if (!pos) return;  //ûÓÐÕâ¸öµã 
        if (T[pos].rec>1) { T[pos].rec--; T[pos].sum--; return; }  //Õâ¸öµã¶àÓÚÒ»¸ö£ºÉ¾³ýÁ˲»»á³öÏÖ¿Õȱ 
        else {
            if (!T[pos].ch[0] && !T[pos].ch[1]) {root=0; return;}  //ɾ³ýÁ˱ä¿ÕÊ÷ 
            else if (!T[pos].ch[0]) {root=T[pos].ch[1]; T[root].fa=0; return;}  //ûÓÐ×ó¶ù×Ó£¬ÓÒ¶ù×ÓÖ±½ÓÉÏλ 
            else {  //ûÓÐÓÒ¶ù×Ó£ºÔÚËüµÄ×ó¶ù×ÓÖÐÕÒµ½×î´óµÄ£¬Ðýתµ½¸ù£¬°ÑËüµÄÓÒ¶ù×Óµ±×ö¸ù(Ò²¾ÍÊÇËü×î´óµÄ×ó¶ù×Ó)µÄÓÒ¶ù×Ó
                int left=T[pos].ch[0];
                while (T[left].ch[1]) left=T[left].ch[1];
                splay(left,T[pos].ch[0]);
                connect(T[pos].ch[1],left,1);
                connect(left,0,1);
                update(left);
            } 
        }
    }
    int Rank(int x) {  //·µ»ØֵΪxµÄ×îСÅÅÃû 
        int now=root,ans=0;  //ansÊÇÒ»±ß×ßÒ»±ßÀÛ¼Ó´ð°¸ 
        while (1) {
            if (T[now].val==x) {ans+=T[T[now].ch[0]].sum; splay(now,root); return ans+1;}  //ÕÒµ½·µ»Ø 
            int nxt=x<T[now].val?0:1;
            if (nxt==1) ans+=T[T[now].ch[0]].sum+T[now].rec;  //ÏÂÒ»²½ÍùÓÒÀÛ¼Ó´ð°¸ 
            now=T[now].ch[nxt];
        }
    }
    int Kth(int x) {  //·µ»ØÏÖÔÚµÚxСµÄÊýÖµ 
        int now=root;
        while (1) {
            int used=T[now].sum-T[T[now].ch[1]].sum;  //×ó×ÓÊ÷¼°xµã×Ü´óС 
            if (T[T[now].ch[0]].sum<x && x<=used) {splay(now,root); return T[now].val; }  //¼ÐÔÚÖм䣺´ð°¸¾ÍÊǸõã 
            if (x<used) now=T[now].ch[0];  //ÔÚ×ó±ß 
            else now=T[now].ch[1],x-=used;  //ÔÚÓÒ±ß 
        }
    }
    int lower(int x) {  //Ñ°ÕÒxµÄÇ°ÇýÖµ£ºÐ¡ÓÚxÇÒ×î´óµÄÖµ 
        int now=root,ans=-INF;
        while (now) {
            if (T[now].val<x) ans=max(ans,T[now].val);  //Ò»±ßÕÒ×î´óµÄ 
            int nxt=x<=T[now].val?0:1;  //Ò»±ßÏòÏÂ×ß 
            now=T[now].ch[nxt];
        }
        return ans;
    }
    int upper(int x) {  //Ñ°ÕÒxµÄºó¼ÌÖµ£º´óÓÚxÇÒ×îСµÄÖµ 
        int now=root,ans=INF;
        while (now) {
            if (T[now].val>x) ans=min(ans,T[now].val);  //Ò»±ßÕÒ×îСµÄ 
            int nxt=x<T[now].val?0:1;  //Ò»±ßÏòÏÂ×ß 
            now=T[now].ch[nxt];
        }
        return ans;
    }
     
    int main()
    {
        cin>>n;
        for (int i=1;i<=n;i++) {
            int opt,x; scanf("%d%d",&opt,&x);
            if (opt==1) Insert(x);
            if (opt==2) Delete(x);
            if (opt==3) printf("%d
    ",Rank(x));
            if (opt==4) printf("%d
    ",Kth(x));
            if (opt==5) printf("%d
    ",lower(x));
            if (opt==6) printf("%d
    ",upper(x));
        }
        return 0;
    }
    View Code

    洛谷P2234[HNOI]营业额统计

    这题就是每个数在前面的数找前驱和后缀更靠近的哪一个。Splay裸题。

    #include<bits/stdc++.h>
    #define root T[0].ch[1]
    using namespace std;
    const int N=1e5+10;
    const int INF=0x3f3f3f3f;
    int n,m;
    struct node{
        int fa,ch[2],val,rec,sum;  //rec是该点大小,sum是该点子树大小 
    }T[N];
    int tot=0;
    void update(int x) { T[x].sum=T[T[x].ch[0]].sum+T[T[x].ch[1]].sum+T[x].rec; }
    int ident(int x) { return T[T[x].fa].ch[0]==x?0:1; }  //返回x是fa的哪个儿子 
    void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; }  //x节点将成为fa节点的how孩子
    void rotate(int x) {  //把x往上旋转(包括左右旋) 
        int Y=T[x].fa,R=T[Y].fa;
        int Yson=ident(x),Rson=ident(Y); 
        connect(T[x].ch[Yson^1],Y,Yson);
        connect(Y,x,Yson^1);
        connect(x,R,Rson);
        update(Y); update(x);
    }
    void splay(int x,int to) {  //伸展操作:把x双旋到to 
        to=T[to].fa;
        while (T[x].fa!=to) {
            int y=T[x].fa;
            if (T[y].fa==to) rotate(x);
            else if (ident(x)==ident(y)) rotate(y),rotate(x);
            else rotate(x),rotate(x);
        }
    }
    int newnode(int v,int f) {  //新建结点值为v,fa为f 
        T[++tot].fa=f;
        T[tot].rec=T[tot].sum=1;
        T[tot].val=v;
        return tot;
    }
    void Insert(int x) {  //插入值为x的结点 
        int now=root;
        if (root==0) newnode(x,0),root=tot;
        else {
            while (1) {
                T[now].sum++;
                if (T[now].val==x) { T[now].rec++; splay(now,root); return; }
                int nxt=x<T[now].val?0:1;  //根据值的大小继续往下插入 
                if (!T[now].ch[nxt]) {  //发现是空点,创建新点 
                    int p=newnode(x,now);
                    T[now].ch[nxt]=p;
                    splay(p,root); return;
                }
                now=T[now].ch[nxt];  //否则继续往下插入 
            }
        }
    }
    int find(int x) {  //查找值为x的位置并返回 
        int now=root;
        while (1) {
            if (!now) return 0;  //没有这个点 
            if (T[now].val==x) { splay(now,root); return now; }  //找到了 
            int nxt=x<T[now].val?0:1;  //根据值继续往下找 
            now=T[now].ch[nxt];
        }
    }
    void Delete(int x) {  //删除值为x的点 
        int pos=find(x);  //先找到这个点的位置 
        if (!pos) return;  //没有这个点 
        if (T[pos].rec>1) { T[pos].rec--; T[pos].sum--; return; }  //这个点多于一个:删除了不会出现空缺 
        else {
            if (!T[pos].ch[0] && !T[pos].ch[1]) {root=0; return;}  //删除了变空树 
            else if (!T[pos].ch[0]) {root=T[pos].ch[1]; T[root].fa=0; return;}  //没有左儿子,右儿子直接上位 
            else {  //没有右儿子:在它的左儿子中找到最大的,旋转到根,把它的右儿子当做根(也就是它最大的左儿子)的右儿子
                int left=T[pos].ch[0];
                while (T[left].ch[1]) left=T[left].ch[1];
                splay(left,T[pos].ch[0]);
                connect(T[pos].ch[1],left,1);
                connect(left,0,1);
                update(left);
            } 
        }
    }
    int Rank(int x) {  //返回值为x的最小排名 
        int now=root,ans=0;  //ans是一边走一边累加答案 
        while (1) {
            if (T[now].val==x) {ans+=T[T[now].ch[0]].sum; splay(now,root); return ans+1;}  //找到返回 
            int nxt=x<T[now].val?0:1;
            if (nxt==1) ans+=T[T[now].ch[0]].sum+T[now].rec;  //下一步往右累加答案 
            now=T[now].ch[nxt];
        }
    }
    int Kth(int x) {  //返回现在第x小的数值 
        int now=root;
        while (1) {
            int used=T[now].sum-T[T[now].ch[1]].sum;  //左子树及x点总大小 
            if (T[T[now].ch[0]].sum<x && x<=used) {splay(now,root); return T[now].val; }  //夹在中间:答案就是该点 
            if (x<used) now=T[now].ch[0];  //在左边 
            else now=T[now].ch[1],x-=used;  //在右边 
        }
    }
    int lower(int x) {  //寻找x的前驱值:小于x且最大的值 
        int now=root,ans=-INF;
        while (now) {
            if (T[now].val<x) ans=max(ans,T[now].val);  //一边找最大的 
            int nxt=x<=T[now].val?0:1;  //一边向下走 
            now=T[now].ch[nxt];
        }
        return ans;
    }
    int upper(int x) {  //寻找x的后继值:大于x且最小的值 
        int now=root,ans=INF;
        while (now) {
            if (T[now].val>x) ans=min(ans,T[now].val);  //一边找最小的 
            int nxt=x<T[now].val?0:1;  //一边向下走 
            now=T[now].ch[nxt];
        }
        return ans;
    }
     
    int main()
    {
        cin>>n;
        long long ans=0;
        for (int i=1;i<=n;i++) {
            int x; scanf("%d",&x);
            if (i==1) ans+=x;
            else if (find(x)) ans+=0;
            else ans+=min(x-lower(x),upper(x)-x); 
            Insert(x);
        } 
        cout<<ans<<endl;
        return 0;
    }
    View Code

    洛谷2286[HNOI]宠物收留场

    宠物有冗余的时候把宠物插到平衡树上,人有冗余的时候把人插到平衡树上,分清这个逻辑之后就是套板子的事。

    #include<bits/stdc++.h>
    #define root T[0].ch[1]
    using namespace std;
    const int N=1e5+10;
    const int MOD=1e6;
    const int INF=0x3f3f3f3f;
    int n,m;
    struct node{
        int fa,ch[2],val,rec,sum;  //rec是该点大小,sum是该点子树大小 
    }T[N];
    int tot=0;
    void update(int x) { T[x].sum=T[T[x].ch[0]].sum+T[T[x].ch[1]].sum+T[x].rec; }
    int ident(int x) { return T[T[x].fa].ch[0]==x?0:1; }  //返回x是fa的哪个儿子 
    void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; }  //x节点将成为fa节点的how孩子
    void rotate(int x) {  //把x往上旋转(包括左右旋) 
        int Y=T[x].fa,R=T[Y].fa;
        int Yson=ident(x),Rson=ident(Y); 
        connect(T[x].ch[Yson^1],Y,Yson);
        connect(Y,x,Yson^1);
        connect(x,R,Rson);
        update(Y); update(x);
    }
    void splay(int x,int to) {  //伸展操作:把x双旋到to 
        to=T[to].fa;
        while (T[x].fa!=to) {
            int y=T[x].fa;
            if (T[y].fa==to) rotate(x);
            else if (ident(x)==ident(y)) rotate(y),rotate(x);
            else rotate(x),rotate(x);
        }
    }
    int newnode(int v,int f) {  //新建结点值为v,fa为f 
        T[++tot].fa=f;
        T[tot].rec=T[tot].sum=1;
        T[tot].val=v;
        return tot;
    }
    void Insert(int x) {  //插入值为x的结点 
        int now=root;
        if (root==0) newnode(x,0),root=tot;
        else {
            while (1) {
                T[now].sum++;
                if (T[now].val==x) { T[now].rec++; splay(now,root); return; }
                int nxt=x<T[now].val?0:1;  //根据值的大小继续往下插入 
                if (!T[now].ch[nxt]) {  //发现是空点,创建新点 
                    int p=newnode(x,now);
                    T[now].ch[nxt]=p;
                    splay(p,root); return;
                }
                now=T[now].ch[nxt];  //否则继续往下插入 
            }
        }
    }
    int find(int x) {  //查找值为x的位置并返回 
        int now=root;
        while (1) {
            if (!now) return 0;  //没有这个点 
            if (T[now].val==x) { splay(now,root); return now; }  //找到了 
            int nxt=x<T[now].val?0:1;  //根据值继续往下找 
            now=T[now].ch[nxt];
        }
    }
    void Delete(int x) {  //删除值为x的点 
        int pos=find(x);  //先找到这个点的位置 
        if (!pos) return;  //没有这个点 
        if (T[pos].rec>1) { T[pos].rec--; T[pos].sum--; return; }  //这个点多于一个:删除了不会出现空缺 
        else {
            if (!T[pos].ch[0] && !T[pos].ch[1]) {root=0; return;}  //删除了变空树 
            else if (!T[pos].ch[0]) {root=T[pos].ch[1]; T[root].fa=0; return;}  //没有左儿子,右儿子直接上位 
            else {  //没有右儿子:在它的左儿子中找到最大的,旋转到根,把它的右儿子当做根(也就是它最大的左儿子)的右儿子
                int left=T[pos].ch[0];
                while (T[left].ch[1]) left=T[left].ch[1];
                splay(left,T[pos].ch[0]);
                connect(T[pos].ch[1],left,1);
                connect(left,0,1);
                update(left);
            } 
        }
    }
    int Rank(int x) {  //返回值为x的最小排名 
        int now=root,ans=0;  //ans是一边走一边累加答案 
        while (1) {
            if (T[now].val==x) {ans+=T[T[now].ch[0]].sum; splay(now,root); return ans+1;}  //找到返回 
            int nxt=x<T[now].val?0:1;
            if (nxt==1) ans+=T[T[now].ch[0]].sum+T[now].rec;  //下一步往右累加答案 
            now=T[now].ch[nxt];
        }
    }
    int Kth(int x) {  //返回现在第x小的数值 
        int now=root;
        while (1) {
            int used=T[now].sum-T[T[now].ch[1]].sum;  //左子树及x点总大小 
            if (T[T[now].ch[0]].sum<x && x<=used) {splay(now,root); return T[now].val; }  //夹在中间:答案就是该点 
            if (x<used) now=T[now].ch[0];  //在左边 
            else now=T[now].ch[1],x-=used;  //在右边 
        }
    }
    int lower(int x) {  //寻找x的前驱值:小于x且最大的值 
        int now=root,ans=-INF;
        while (now) {
            if (T[now].val<x) ans=max(ans,T[now].val);  //一边找最大的 
            int nxt=x<=T[now].val?0:1;  //一边向下走 
            now=T[now].ch[nxt];
        }
        return ans;
    }
    int upper(int x) {  //寻找x的后继值:大于x且最小的值 
        int now=root,ans=INF;
        while (now) {
            if (T[now].val>x) ans=min(ans,T[now].val);  //一边找最小的 
            int nxt=x<T[now].val?0:1;  //一边向下走 
            now=T[now].ch[nxt];
        }
        return ans;
    }
    
    int getit(int x) {
        int t1=lower(x),t2=upper(x);
        if (x-t1<=t2-x) {
            Delete(t1);
            return x-t1;
        } else {
            Delete(t2);
            return t2-x;
        }
    }
    
    int main()
    {
        cin>>n;
        int pet=0,pop=0,ans=0;
        for (int i=1;i<=n;i++) {
            int opt,x; scanf("%d%d",&opt,&x);
            if (opt==0) {  //pet
                if (pop==0) Insert(x),pet++;
                else ans+=getit(x),pop--;
            } else {  //people
                if (pet==0) Insert(x),pop++;
                else ans+=getit(x),pet--;
            }
            ans%=MOD;
        }
        cout<<ans<<endl;
        return 0;
    }
    View Code

    Splay被称为“序列之王”,在区间问题上的应用十分广泛灵活。同等地,Splay不那么容易理解而且代码复杂容易写错常数大,必须要多多练习才能熟练应用。

    入门Splay区间题:洛谷P3391文艺平衡树

    首先按照位置即下标作为关键字建立平衡树,对于需要旋转的一个区间[l,r],把l-1旋转到根节点,把r+1旋转到根节点的右儿子,那么根据平衡树的特点,此时根节点的右儿子的左子树比r+1小比l-1大就肯定是区间[l,r],所以把根节点的右儿子的左子树打上lazy-tag翻转标记,像线段树那样向下传递即可。

    #include<bits/stdc++.h>
    #define root T[0].ch[1]
    using namespace std;
    const int N=1e5+10;
    const int INF=0x3f3f3f3f;
    int n,m;
    struct node{
        int fa,ch[2],val,rec,sum;  //rec是该点大小,sum是该点子树大小 
        int tag;
    }T[N];
    int tot=0;
    void update(int x) { T[x].sum=T[T[x].ch[0]].sum+T[T[x].ch[1]].sum+T[x].rec; }
    int ident(int x) { return T[T[x].fa].ch[0]==x?0:1; }  //返回x是fa的哪个儿子 
    void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; }  //x节点将成为fa节点的how孩子
    void rotate(int x) {  //把x往上旋转(包括左右旋) 
        int Y=T[x].fa,R=T[Y].fa;
        int Yson=ident(x),Rson=ident(Y); 
        connect(T[x].ch[Yson^1],Y,Yson);
        connect(Y,x,Yson^1);
        connect(x,R,Rson);
        update(Y); update(x);
    }
    void splay(int x,int to) {  //伸展操作:把x双旋到to 
        to=T[to].fa;
        while (T[x].fa!=to) {
            int y=T[x].fa;
            if (T[y].fa==to) rotate(x);
            else if (ident(x)==ident(y)) rotate(y),rotate(x);
            else rotate(x),rotate(x);
        }
    }
    void pushdown(int x) {  //向下传递标记 
        if (T[x].tag) {
            swap(T[x].ch[0],T[x].ch[1]);
            T[T[x].ch[0]].tag^=1;
            T[T[x].ch[1]].tag^=1;
            T[x].tag=0;
        }
    }
    
    int getval(int x) {  //返回中序遍历为x的所在结点 
        int now=root;
        pushdown(now);  //记得把标记向下传递 
        while (x!=T[T[now].ch[0]].sum+1) {
            if (T[T[now].ch[0]].sum<x) x-=T[T[now].ch[0]].sum+T[now].rec,now=T[now].ch[1];
            else now=T[now].ch[0];
            pushdown(now);  //记得把标记向下传递 
        }
        return now;
    }
    
    int build(int l,int r) {  //以下标[l,r]为关键字建树 
        if (l>r) return 0;
        int mid=(l+r)>>1;
        connect(build(l,mid-1),mid,0);  //左子树练到父亲mid 
        connect(build(mid+1,r),mid,1);    //右子树连到父亲mid 
        T[mid].val=mid;
        T[mid].rec=1;  //mid这个点的大小
        T[mid].tag=0;  //刚开始没有翻转标记 
        update(mid);
        return mid;
    }
    
    void rever(int x,int y) {  //把位置区间[x,y]翻转 
        int l=getval(x-1),r=getval(y+1);  //找到翻转区间两端的结点 
        splay(l,root); //l旋到树根
        splay(r,T[root].ch[1]);  //把r旋到树根右儿子 
        T[T[T[root].ch[1]].ch[0]].tag^=1;  //此时根节点的右儿子的左儿子所代表的就是区间l,r
    }
    
    int main()
    {
        cin>>n>>m;
        root=build(1,n+2);  //用2-n+1代表1-n的元素
        for (int i=1;i<=m;i++) {
            int x,y; scanf("%d%d",&x,&y);
            rever(x+1,y+1);
        }
        for (int i=1;i<=n;i++) 
            printf("%d ",T[getval(i+1)].val-1);
        return 0;
    }
    View Code

    洛谷P3165[CQOI2014]排序机械臂

    这道题非常有意思。使我对平衡树的理解更加深了,首先得明白平衡树一旦建树的时候结构确定了,经过旋转伸展等操作是不会改变它的平衡性的(亦即是这棵树的中序遍历就不会因为旋转伸展操作改变)。而且对于平衡树来说重要的是中序遍历的顺序是确定的有大小平衡关系的,但是中序遍历的编号并不是要求有大小关系的,就是  中序遍历≠中序遍历编号,平衡树结点的编号是可以随便定的,只要这些编号的相对顺序确定那么中序遍历的顺序就确定了 。(这一点或许大神一下子就明白了,但是确实花了蒟蒻一些时间去理解)。

    说回这一题,我们很容易想到一个显然的做法:按顺序找最小的,次小的,第三小的......然后依次翻转区间,也能显然发现这个做法的难点在于这里的区间是是会变化的,我们根本不知道最小的位置在哪,次小的位置在哪?。。。。。。

    看回我们第一段的结论,平衡树的编号是可以随便定的,那么我们就直接让最小的结点编号为1,次小的编号为2,以此类推。然后注意建树的时候还是得按照原数组相对顺序建树(这是区间翻转的前提),但是注意这时我们将要做的翻转操作和结点编号是一一对应的!那么此时我们只要知道结点1在区间的位置(这也是该点在平衡树中序遍历的位置,也是该点的名次),然后翻转即可。问题就变成怎么找到这个点的名次?答案是把这个点splay到树根,那么树根左子树Size加一就是它的名次。于是此题可解!

    这题还有一些小细节,例如因为输出答案的时候有可能没有下传标记所以splay操作的时候要顺便下传标记。具体可以看代码:

    #include<bits/stdc++.h>
    #define root T[0].ch[1]
    using namespace std;
    const int N=1e5+10;
    const int INF=0x3f3f3f3f;
    int n,m,b[N];
    struct node{
        int fa,ch[2],val,rec,sum;  //rec是该点大小,sum是该点子树大小 
        int tag;
    }T[N];
    struct dat{
        int val,pos;
        bool operator < (const dat &rhs) const {
            return val<rhs.val || val==rhs.val && pos<rhs.pos;
        }
    }a[N];
    int tot=0;
    void update(int x) { T[x].sum=T[T[x].ch[0]].sum+T[T[x].ch[1]].sum+T[x].rec; }
    int ident(int x) { return T[T[x].fa].ch[0]==x?0:1; }  //返回x是fa的哪个儿子 
    void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; }  //x节点将成为fa节点的how孩子
    void pushdown(int x) {  //向下传递标记 
        if (x && T[x].tag) {  //这里需要注意:0是一个虚点,这个点一切信息都是不能用的,除了恰好用T[0].ch[1]=root,这是因为root的fa一定是T[0] 
            swap(T[x].ch[0],T[x].ch[1]);
            T[T[x].ch[0]].tag^=1;
            T[T[x].ch[1]].tag^=1;
            T[x].tag=0;
        }
    }
    void rotate(int x) {  //把x往上旋转(包括左右旋) 
        int Y=T[x].fa,R=T[Y].fa;
        int Yson=ident(x),Rson=ident(Y); 
        connect(T[x].ch[Yson^1],Y,Yson);
        connect(Y,x,Yson^1);
        connect(x,R,Rson);
        update(Y); update(x);
    }
    void splay(int x,int to) {  //伸展操作:把x双旋到to 
        to=T[to].fa;
        while (T[x].fa!=to) {
            int y=T[x].fa;
            pushdown(T[y].fa); pushdown(y); pushdown(x);  //把标记往下传递 
            if (T[y].fa==to) rotate(x);
            else if (ident(x)==ident(y)) rotate(y),rotate(x);
            else rotate(x),rotate(x);
        }
    }
    
    int getval(int x) {  //返回中序遍历为x的所在结点 
        int now=root;
        pushdown(now);  //记得把标记向下传递 
        while (x!=T[T[now].ch[0]].sum+1) {
            if (T[T[now].ch[0]].sum<x) x-=T[T[now].ch[0]].sum+T[now].rec,now=T[now].ch[1];
            else now=T[now].ch[0];
            pushdown(now);  //记得把标记向下传递 
        }
        return now;
    }
    
    int build(int l,int r) {  //以下标[l,r]为关键字建树 
        if (l>r) return 0;
        int mid=(l+r)>>1;
        int nowid=b[mid];
        if (mid>l) connect(build(l,mid-1),nowid,0);  //左子树练到父亲nowid 
        if (r>mid) connect(build(mid+1,r),nowid,1);  //右子树连到父亲nowid
        T[nowid].rec=1;  //mid这个点的大小
        T[nowid].tag=0;  //刚开始没有翻转标记 
        update(nowid);
        return nowid;
    }
    
    void rever(int x,int y) {  //把位置(在平衡树上就是中序遍历)区间[x,y]翻转 
        int l=getval(x-1),r=getval(y+1);  //找到翻转区间两端的结点 
        splay(l,root); //l旋到树根
        splay(r,T[root].ch[1]);  //把r旋到树根右儿子 
        T[T[T[root].ch[1]].ch[0]].tag^=1;  //此时根节点的右儿子的左儿子所代表的就是区间l,r
    }
    
    int main()
    {
        cin>>n;
        for (int i=1;i<=n;i++) scanf("%d",&a[i].val),a[i].pos=i;
        sort(a+1,a+n+1);
        //这里的b数组是理解平衡树的关键:我们仍然是以位置为关键字建树(即排序还是按照位置大小)
        //但是结点编号是b数组确定的,需要注意的是尽管编号是乱序的但并不影响这棵树还是以位置为关键字平衡的,只是这棵树上挂的点编号不按顺序 
        for (int i=1;i<=n;i++) b[a[i].pos+1]=i+1;
        b[1]=1; b[n+2]=n+2;
        
        root=build(1,n+2);  //用2-n+1代表1-n的元素
        for (int i=1;i<=n;i++) {
            splay(b[a[i].pos+1],root);
            int rnk=T[T[root].ch[0]].sum+T[root].rec;
            rever(i+1,rnk);
            printf("%d ",rnk-1);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    concurrent.futures
    HTTP协议
    Web框架原理
    Docker从入门到实战应用
    Mac Homebrew超坑爹的地方
    第6章-7.找出总分最高的学生 (15分)
    第6章-6.求指定层的元素个数 (40分)
    第6章-5.列表元素个数的加权和(1) (40分)
    第6章-4.列表数字元素加权和(1) (40分)
    第6章-3.列表或元组的数字元素求和 (20分)
  • 原文地址:https://www.cnblogs.com/clno1/p/10893614.html
Copyright © 2011-2022 走看看