zoukankan      html  css  js  c++  java
  • Splay学习笔记

    写在前面

    蒟蒻现在才开始学splay[我真的好菜呀]

    这是一篇有关splay入门学习的笔记qwq

    就让splay开始吧?

    首先要学习splay之前,要先知道splay是个什么玩意儿……

    我们都知道二叉查找树的性质:左小右大 所以我们遍历二叉查找树的中序遍历是一个从小到大的序列qwq

    二叉查找树的操作的时间复杂度是O(h)的

    但是当出题人非常不良心[误]数据比较大的时候 直接使用二叉查找树显然会T……

    因为当你的树被建成这样的时候,就gg了……

    所以我们要引入平衡树的概念……

    其实所谓的平衡树就是更加“平衡”的二叉搜索树

    那么我们要怎么让这棵树更加“平衡”呢?

    旋转这棵树上的节点!

    rotate操作

    rotate操作是splay的灵魂啊

    我们考虑把一个点旋转到它的父亲会发生什么……

    如果这个节点是它父亲的右儿子,那么把它旋到父亲之后,它的父亲就没有右儿子了

    那么怎么办呢……

    再想想,这个时候我们把它旋转到了父亲,原本的父亲就是它的左儿子啦[因为它比父亲大]

    这个时候它的左儿子要往哪放呢……

    这时候我们想起来父亲的右儿子没了……

    对!就把它放到父亲的右儿子

    这棵树仍然保持了二叉搜索树的性质

    因为本来儿子节点的左子树小于儿子节点,但是大于父亲节点,所以把它塞到柚子树右子树是没有错的

    然后这棵二叉搜索树的性质就是没错的啦x

    图大概这样……假设我们要把A旋到B 

     第二幅图是旋转之后的样子 左旋和右旋同理

    接下来稍微看一下代码吧……

    void rotate(int x)
    {
        int fa1=fa[x];
        int fa2=fa[fa1];
        int K=Get(x);
        int K1=Get(fa1);
        Son[fa1][K]=Son[x][K^1];
        fa[Son[fa1][K]]=fa1;
        Son[x][K^1]=fa1;
        fa[fa1]=x;
        fa[x]=fa2;
        if (fa2) Son[fa2][Son[fa2][1]==fa1]=x;
        Pushup(fa1);
        Pushup(x);
    }

    Pushup函数是更新这个节点的值 具体看代码

    void Pushup(int x)
    {
        if (x)
        {
            Size[x]=cnt[x];
            if (Son[x][0]) Size[x]+=Size[Son[x][0]];
            if (Son[x][1]) Size[x]+=Size[Son[x][1]];
        }
    }

    Get函数是得到左右儿子

    bool Get(int Now)
    {
        return Son[fa[Now]][1]==Now;
    }

    note:记得祖孙三代的值都要更新

    cnt存在是因为二叉搜索树上不能有重复的数,于是我们就……在遇到同样的数的时候把cnt++!

    好的 灵魂操作解决了接下来就相对轻松啦!

    splay操作

    splay操作的实质其实就是把一个节点一路旋转到目标节点

    唯一需要注意的点是如果出现祖孙三代三点同在一条线上的时候,先旋转父亲再旋转自己,否则旋转两次自己

    代码:

    void Splay(int Now,int goal)
    {
        for (int Fa;(Fa=fa[Now])!=goal;rotate(Now))
        if (fa[Fa]!=goal)
            if (Get(Now)==Get(Fa))
            rotate(Fa);
            else rotate(Now);
        if (!goal) rt=Now;
    }

    !goal的时候表示Now这个节点旋到根节点去了……

    insert操作

    insert操作的时候需要分类讨论一下

    如果这是这棵树里面唯一的节点,这个点就是根

    如果这个值已经在树里出现过了,那么就把它的cnt++

    否则就和二叉搜索树一样把它插入到相应的位置就OK了

    代码:

    void Insert(int Now)
    {
        if (rt==0)
        {
            size++;
            key[size]=Now;
            rt=size;
            cnt[size]=Size[size]=1;
            fa[size]=Son[size][0]=Son[size][1]=0;
            return;
        }
        int now=rt,Fa=0;
        while (1)
        {
            if (Now==key[now])
            {
                cnt[now]++;
                Pushup(now);Pushup(Fa);
                Splay(now);
                return;
            }
        Fa=now;now=Son[now][key[now]<Now];
        if (now==0)
            {
                size++;
                Size[size]=cnt[size]=1;
                Son[size][0]=Son[size][1]=0;
                Son[Fa][Now>key[Fa]]=size;
                fa[size]=Fa;
                key[size]=Now;
                Pushup(Fa);
                Splay(size);
                return;
            }
        }
    }

    动态求解K大和求解K在树中的排名

    这两个操作是有点对称的……

    有点像权值线段树的操作,如果左边够就往左边走,否则就把左边的扣掉往右边走

    具体直接看代码应该挺好理解的?

    K大:

    int Kth(int Now)
    {
        int now=rt;
        while (1)
        {
            if (Son[now][0]&&Now<=Size[Son[now][0]])
                now=Son[now][0];
            else
            {
                int temp=Size[Son[now][0]]+cnt[now];
                if (Now<=temp)
                    return key[now];
                Now-=temp;
                now=Son[now][1];
            }
        }
    }

    rank:

    int Rank(int Now)
    {
        int now=rt,ans=0;
        while (1)
        {
            if (Now<key[now])
                now=Son[now][0];
            else
            {
                ans+=Size[Son[now][0]];
                if (Now==key[now])
                {
                    Splay(now);
                    return ans+1;
                }
            ans+=cnt[now];
            now=Son[now][1];
            }
        }
    }

    求解前驱和后继

    因为splay完之后这个点的已经在根上了,它的前驱就是这个左子树中最右边的那个,后继就是柚子树右子树的最左边那个orz

    pre:

    int Pre()
    {
        int now=Son[rt][0];
        while (Son[now][1]) now=Son[now][1];
        return now;
    }

    在求解前驱之前记得先把这个点插入进树里 顺便把他旋转到根

    if (opt==5)
            {
                Insert(x);
                printf("%d
    ",key[Pre()]);
                del(x);
            }

    nex:

    int nex()
    {
        int now=Son[rt][1];
        while (Son[now][0]) now=Son[now][0];
        return now;
    }

    同理在主程序中的处理:

    if (opt==6)
            {
                Insert(x);
                printf("%d
    ",key[nex()]);
                del(x);
            }

    delete操作

    这是个比较麻烦的操作,所以我会尽量讲的清楚点orz

    和insert一样,我们还是考虑分类讨论

    先把要被删除的节点旋到根

    如果树中被删除的值不止一个 直接把cnt--就完事了

    如果被删除的数是树中唯一的值,把它删掉,树置为空

    如果被删除的树只有左子树或者右子树,把它删了把左子树/右子树换成根就完事了

    如果被删除的树同时有左右子树就先把它删了,把左子树旋到根上,然后把原本的右子树扔个新的根节点

    然后分这四种情况讨论就OK啦

    代码:

    void del(int Now)
    {
        Rank(Now);
        if (cnt[rt]>1)
        {
            cnt[rt]--;
            Pushup(rt);
            return;
        }
        if (!Son[rt][0]&&!Son[rt][1])
        {
            Clear(rt);rt=0;return;
        }
        if (!Son[rt][0])
        {
            int Ort=rt;
            rt=Son[rt][1];
            fa[rt]=0;
            Clear(Ort);
            return;
        }
        else if (!Son[rt][1])
        {
            int Ort=rt;
            rt=Son[rt][0];
            fa[rt]=0;
            Clear(Ort);
            return;
        }
        int Ort=rt;
        int Lb=Pre();
        Splay(Lb);
        Son[rt][1]=Son[Ort][1];
        fa[Son[Ort][1]]=rt;
        Clear(Ort);
        Pushup(Ort);
    }

    code[写的很丑]

    #include <bits/stdc++.h>
    using namespace std;
    int Size[1000005],size;
    int Son[1000005][2],cnt[1000005],fa[1000005],rt,key[1000005];
    void Clear(int x)
    {
        fa[x]=0;
        cnt[x]=0;
        key[x]=0;
        Son[x][0]=Son[x][1]=0;
        Size[x]=0;
    }
    void Pushup(int x)
    {
        if (x)
        {
            Size[x]=cnt[x];
            if (Son[x][0]) Size[x]+=Size[Son[x][0]];
            if (Son[x][1]) Size[x]+=Size[Son[x][1]];
        }
    }
    bool Get(int Now)
    {
        return Son[fa[Now]][1]==Now;
    }
    void rotate(int x)
    {
        int fa1=fa[x];
        int fa2=fa[fa1];
        int K=Get(x);
        int K1=Get(fa1);
        Son[fa1][K]=Son[x][K^1];
        fa[Son[fa1][K]]=fa1;
        Son[x][K^1]=fa1;
        fa[fa1]=x;
        fa[x]=fa2;
        if (fa2) Son[fa2][Son[fa2][1]==fa1]=x;
        Pushup(fa1);
        Pushup(x);
    }
    void Splay(int Now)
    {
        for (int Fa;Fa=fa[Now];rotate(Now))
        if (fa[Fa])
            if (Get(Now)==Get(Fa))
            rotate(Fa);
            else rotate(Now);
        rt=Now;
    }
    void Insert(int Now)
    {
        if (rt==0)
        {
            size++;
            key[size]=Now;
            rt=size;
            cnt[size]=Size[size]=1;
            fa[size]=Son[size][0]=Son[size][1]=0;
            return;
        }
        int now=rt,Fa=0;
        while (1)
        {
            if (Now==key[now])
            {
                cnt[now]++;
                Pushup(now);Pushup(Fa);
                Splay(now);
                return;
            }
        Fa=now;now=Son[now][key[now]<Now];
        if (now==0)
            {
                size++;
                Size[size]=cnt[size]=1;
                Son[size][0]=Son[size][1]=0;
                Son[Fa][Now>key[Fa]]=size;
                fa[size]=Fa;
                key[size]=Now;
                Pushup(Fa);
                Splay(size);
                return;
            }
        }
    }
    int Rank(int Now)
    {
        int now=rt,ans=0;
        while (1)
        {
            if (Now<key[now])
                now=Son[now][0];
            else
            {
                ans+=Size[Son[now][0]];
                if (Now==key[now])
                {
                    Splay(now);
                    return ans+1;
                }
            ans+=cnt[now];
            now=Son[now][1];
            }
        }
    }
    int Kth(int Now)
    {
        int now=rt;
        while (1)
        {
            if (Son[now][0]&&Now<=Size[Son[now][0]])
                now=Son[now][0];
            else
            {
                int temp=Size[Son[now][0]]+cnt[now];
                if (Now<=temp)
                    return key[now];
                Now-=temp;
                now=Son[now][1];
            }
        }
    }
    int Pre()
    {
        int now=Son[rt][0];
        while (Son[now][1]) now=Son[now][1];
        return now;
    }
    int nex()
    {
        int now=Son[rt][1];
        while (Son[now][0]) now=Son[now][0];
        return now;
    }
    void del(int Now)
    {
        Rank(Now);
        if (cnt[rt]>1)
        {
            cnt[rt]--;
            Pushup(rt);
            return;
        }
        if (!Son[rt][0]&&!Son[rt][1])
        {
            Clear(rt);rt=0;return;
        }
        if (!Son[rt][0])
        {
            int Ort=rt;
            rt=Son[rt][1];
            fa[rt]=0;
            Clear(Ort);
            return;
        }
        else if (!Son[rt][1])
        {
            int Ort=rt;
            rt=Son[rt][0];
            fa[rt]=0;
            Clear(Ort);
            return;
        }
        int Ort=rt;
        int Lb=Pre();
        Splay(Lb);
        Son[rt][1]=Son[Ort][1];
        fa[Son[Ort][1]]=rt;
        Clear(Ort);
        Pushup(Ort);
    }
    int main()
    {
        int N;
        scanf("%d",&N);
        while (N--)
        {
            int opt,x;
            scanf("%d%d",&opt,&x);
            if (opt==1)    
                Insert(x);
            if (opt==2)
                del(x);
            if (opt==3)
                printf("%d
    ",Rank(x));
            if (opt==4)
                printf("%d
    ",Kth(x));
            if (opt==5)
            {
                Insert(x);
                printf("%d
    ",key[Pre()]);
                del(x);
            }
            if (opt==6)
            {
                Insert(x);
                printf("%d
    ",key[nex()]);
                del(x);
            }
        }
        return 0;
    }

    题目是luogu P3369,只涉及到最基本的平衡树操作

    区间翻转和lazy tag,下传tag

     我们要翻转一个序列上的[l,r]区间,在splay上要怎么做呢?

    考虑以区间的下标建立起splay,无论怎么变动,中序遍历的区间下标是不变的qwq

    然后考虑交换splay每个点上的数字

    如果暴力修改是O(N)[大概?]的效率

    我们需要更加优秀的修改方式

    想想线段树怎么处理这种问题的?

    lazy tag呀!

    为了方便我们多加入两个虚拟节点-inf 和 inf

    要修改的区间就是[l+1,r+1]

    但是这些区间不在同一个子树下 tag打不上可咋整啊?

    那就把他们旋转到同一个子树下!

    把r+2旋转到根

    再把l旋到r+2的左儿子 这时候[l+1,r+1]就会在根节点的右子树的左子树下了

    可以稍微画图理解一下……

    然后对于这玩意儿打上tag就行了

    然后insert数据的时候还有一个小trick

    如果一个个插入未免太慢了,我们考虑类似于线段树的build去建立一棵splay

    具体实现细节可以看代码qwq

    #include <bits/stdc++.h>
    using namespace std;
    int Size[1000005],size;
    int Son[1000005][2],cnt[1000005],data[1000005],fa[1000005],rt,key[1000005],tag[10000005];
    void Pushup(int x)
    {
            Size[x]=1;
            Size[x]+=Size[Son[x][0]];
            Size[x]+=Size[Son[x][1]];
    }
    void PushDown(int x)
    {
        if (x&&tag[x])
            {
                tag[Son[x][0]]^=1;
                tag[Son[x][1]]^=1;
                swap(Son[x][0],Son[x][1]);
                tag[x]=0;
            }
    }
    bool Get(int Now)
    {
        return Son[fa[Now]][1]==Now;
    }
    void rotate(int x)
    {
        int fa1=fa[x];
        int fa2=fa[fa1];
        PushDown(x);
        PushDown(fa1);
        int K=Get(x);
        int K1=Get(fa1);
        Son[fa1][K]=Son[x][K^1];
        fa[Son[fa1][K]]=fa1;
        Son[x][K^1]=fa1;
        fa[fa1]=x;
        fa[x]=fa2;
        if (fa2) Son[fa2][Son[fa2][1]==fa1]=x;
        Pushup(fa1);
        Pushup(x);
    }
    void Splay(int Now,int goal)
    {
        for (int Fa;(Fa=fa[Now])!=goal;rotate(Now))
        if (fa[Fa]!=goal)
            if (Get(Now)==Get(Fa))
            rotate(Fa);
            else rotate(Now);
        if (!goal) rt=Now;
    }
    int Kth(int Now)
    {
        int now=rt;
        while (1)
        {
            PushDown(now);
            if (Now<=Size[Son[now][0]])
                now=Son[now][0];
            else
            if (Now>Size[Son[now][0]])
            {        
                Now-=Size[Son[now][0]]+1;
                if (!Now)
                    return now;
                now=Son[now][1];
            }
        }
    }
    void Reverse(int l,int r)
    {
        l=Kth(l);        
        r=Kth(r+2);        
        Splay(l,0);
        Splay(r,l);    
        PushDown(rt);
        tag[Son[Son[rt][1]][0]]^=1;
    }
    void Write(int Now)
    {
        PushDown(Now);
        if (Son[Now][0]) Write(Son[Now][0]);
        if (key[Now]!=-1e9&&key[Now]!=1e9)printf("%d ",key[Now]);
        if (Son[Now][1]) Write(Son[Now][1]);
    }
    int Build(int Fa,int l,int r)
    {
        if (l>r) return 0;
        int mid=(l+r)>>1;
        int Now=++size;
        key[Now]=data[mid];
        fa[Now]=Fa;
        tag[Now]=0;
        Son[Now][0]=Build(Now,l,mid-1);
        Son[Now][1]=Build(Now,mid+1,r);
        Pushup(Now);
        return Now;
    }
    int main()
    {
        int N,M;
        scanf("%d%d",&N,&M);
        data[1]=-1e9;    
        for (int i=1;i<=N;i++)
            data[i+1]=i;
        data[N+2]=1e9;
        rt=Build(0,1,N+2);
        while (M--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            Reverse(l,r);
        }
        Write(rt);
        return 0;
    }
  • 相关阅读:
    python中的反射
    ZOJ 3827 Information Entropy 水
    我的软考之路(七)——数据结构与算法(5)之查找
    nginx.conf 集群完整配置
    恼人的函数指针(二)
    C语言100个经典的算法
    spring事务心得积累
    Vue报错:OPTIONS 405 Method Not Allowed以及CORS跨域错误
    IDA脚本dump内存的示例
    lightProxy
  • 原文地址:https://www.cnblogs.com/si--nian/p/10794517.html
Copyright © 2011-2022 走看看