• Splay——学习笔记


    @

    代码实现以及作为基础的平衡树的应用

    模板题

    您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

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

    先贴个代码

    代码如下

    #include<cstdio>
    using namespace std;
    #define ri register int
    #define ll long long
    const int N=200007;
    int n;
    template<class T>inline void read(T &res){
        static char son;T flag=1;
        while((son=getchar())<'0'||son>'9') if(son=='-') flag=-1;
        res=son-48;
        while((son=getchar())>='0'&&son<='9') res=(res<<1)+(res<<3)+son-48;
        res*=flag;
    }
    class Splay{
        public:
            int root,tot=0,fa[N],size[N],cnt[N],val[N],son[N][2];
        private:
            void update(int p){
                size[p]=size[son[p][0]]+size[son[p][1]]+cnt[p];
            }
            int check(int p){
                return p==son[fa[p]][1];
            }
            void rotate(int p){
                int f=fa[p],ff=fa[f],k=check(p),kk=check(f),sp=son[p][k^1];
                son[p][k^1]=f;fa[f]=p;
                son[f][k]=sp;fa[sp]=f;
                son[ff][kk]=p;fa[p]=ff;
                update(f);update(p);
            }
            void splay(int p,int goal){
                if(p==goal) return;//易忘
                while(fa[p]!=goal){
                    int f=fa[p],ff=fa[f];
                    if(ff!=goal){
                        if(check(p)==check(f)) rotate(f);
                        else rotate(p);
                    }
                    rotate(p);
                }
                if(goal==0)	root=p;//易忘 
            }//把p   Splay到goal的儿子
            void find(int x){
                int cur=root;
                while(son[cur][x>val[cur]]&&x!=val[cur])
                    cur=son[cur][x>val[cur]];
                splay(cur,0);
            }
        public:
            int rank(int x){
                find(x);
                return size[son[root][0]]+1;
            }
            void insert(int x){
                int cur=root,p=0;//p是cur的父亲
                while(cur&&x!=val[cur]){
                    p=cur;
                    cur=son[cur][x>val[cur]];
                }
                if(cur) ++cnt[cur];//找到了x 
                else{
                    cur=++tot;
                    son[cur][0]=son[cur][1]=0;
                    fa[cur]=p;
                    val[cur]=x;
                    cnt[cur]=size[cur]=1;//要赋全 
                    if(p) son[p][x>val[p]]=cur;//一定要判断
                }
                splay(cur,0);
            }
            int pre(int x){
                find(x);
                if(val[root]<x) return root;
                int p=son[root][0];
                while(son[p][1]) p=son[p][1];
                return p;
            }//记得返回的是位置而不是实际的值 
            int nxt(int x){
                find(x);
                if(val[root]>x) return root;
                int p=son[root][1];
                while(son[p][0]) p=son[p][0];
                return p;
            }
            void del(int x){
                int pr=pre(x),nt=nxt(x);
                splay(pr,0);splay(nt,pr);
                int p=son[nt][0];
                if(cnt[p]>1){
                    --cnt[p];
                    splay(p,0);
                }
                else son[nt][0]=0;
                update(nt);update(root);
            }
            int search(int rk){
                int p=root;
                while(1){
                    if(son[p][0]&&rk<=size[son[p][0]]) p=son[p][0];//一定要判断是否有儿子 
                    else if(rk>size[son[p][0]]+cnt[p]){
                        rk-=size[son[p][0]]+cnt[p];
                        p=son[p][1];//注意顺序
                    }
                    else return p;
                }
            }
    }Tree;
    int main()
    {
        Tree.insert(2147483647);
        Tree.insert(-2147483647);//为了上下界减少讨论,我们插入两个标兵元素
        read(n);
        while(n--){
            int opt,x;
            read(opt);
            read(x);
            switch(opt){
                case 1:{
                    Tree.insert(x);
                    break;
                }
                case 2:{
                    Tree.del(x);
                    break;
                }
                case 3:{
                    printf("%d\n",Tree.rank(x)-1);
                    break;
                }
                case 4:{
                    printf("%d\n",Tree.val[Tree.search(x+1)]);
                    break;
                }
                case 5:{
                    printf("%d\n",Tree.val[Tree.pre(x)]);
                    break;
                }
                case 6:{
                    printf("%d\n",Tree.val[Tree.nxt(x)]);
                    break;
                }
            }
        }
        return 0;
    }
    

    在维护序列中的应用(重点)

    就像线段树既可以维护一个序列,在序列中完成各种灵活区间操作,也可以作为值域线段树,来维护具体的一些数值;
    \(splay\)同样拥有维护一个序列的功能。

    此时,\(splay\)中的排名不在是具体的数的排名,而是对应了一个数列的相应的位置,它具备很多优良的性质,我会列举几个常用的功能。

    首先,我们要明确一件事,那就是我们维护的区间在什么地方。

    根据平衡树的性质易得 我们的区间就保存在splay的中序遍历中(仔细思考),因为旋转不影响中序遍历。

    提取区间

    这是序列操作的基础(其实在某种程度上比线段树还好写)。

    还记得删除操作吗?其实是一个道理的。例如我们要把区间\([l,r]\)提取出来,那么只需要把排名为\(l-1\)的点转到根节点,再把\(r+1\)转到根节点的儿子结点,根节点右边的就是区间\([l,n]\)(右边都比它大诶),\(r+1\)的左子树就是\([l,r]\)了。

    刚接触时,我对于这些还是很疑惑的,为什么排名对应区间呢?排名不是根据权值调整的吗?
    对于这个问题,我们要明白一件事,就是\(splay\)中排名实质上就是由我们插入的顺序决定的。回想上面的插入操作,我们过去在把\(splay\)作为普通平衡树使用时,只是刻意地把要插入的数调整到了相应位置,以此来保证我们插入的数保证平衡树的性质。
    这里我把自己的问题分享出来,希望和大家共勉。

    代码实现:

    inline int gett(int l,int r){
    	l=search(l-1);
    	r=search(r+1);//找到排名对应的点的序号
    	splay(l,0);
    	splay(r,l);
    	return son[r][0];//区间就包含在r的左子树中
    }
    

    翻转区间

    模板题

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

    翻转一个区间,实质就是把这个区间的所有左右子树交换位置。(试着手动模拟一下)

    翻转一个指定的区间,我们只需要把这个区间先提取出来,再给这个子树的根节点打上一个翻转标记\(tag\)(参考线段树的标记思想)

    最后在所有的操作完成后,对\(splay\)做一次中序遍历,把该下放的标记下放,最后输出中序遍历即可。

    值得一提的是,我们在做旋转操作时,如果旋转了带有翻转标记的结点,那么结果会受到影响。为了避免这样的麻烦,我们就在\(search\)操作(根据排名找点)中,把碰到的每一个点的标记\(pushdown\),那么以后我们就可以为所欲为\(splay\)了。

    理解后代码也很容易了

    #include<bits/stdc++.h>
    using namespace std;
    #define ri register int
    const int N=200001;
    int n,m;
    template<class T>inline void read(T &res){
        static char ch;T flag=1;
        while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1;
        res=ch-48;
        while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-48;
        res*=flag;
    }
    class Splay{
        public: 
        int root=0,ndnum=0,son[N][2],fa[N],size[N],cnt[N],val[N];
        bool tag[N];
        inline void update(int p){
            size[p]=size[son[p][0]]+size[son[p][1]]+cnt[p];
        }
        inline void pushdown(int p){
            if(tag[p]){
                if(son[p][0]) tag[son[p][0]]^=1;
                if(son[p][1]) tag[son[p][1]]^=1;
                swap(son[p][0],son[p][1]);
                tag[p]^=1;
            }
        }
        inline bool check(int p){
            return p==son[fa[p]][1];
        }
        inline void rotate(int p){
            int f=fa[p],ff=fa[f],k=check(p),kk=check(f),nd=son[p][k^1];
            son[ff][kk]=p;fa[p]=ff;
            son[p][k^1]=f;fa[f]=p;
            son[f][k]=nd;fa[nd]=f;
            update(f);update(p);
        }
        inline void splay(int p,int goal){
            if(p==goal) return; 
            while(fa[p]!=goal){
                int f=fa[p],ff=fa[f];
                if(ff!=goal){
                    if(check(p)==check(f))
                        rotate(f);
                    else rotate(p);
                }
                rotate(p);
            }
            if(goal==0) root=p;
        }
        inline int search(int rk){
        	int p=root;
        	while(1){
        		pushdown(p);//标记下放
        		if(son[p][0]&&rk<=size[son[p][0]]) p=son[p][0];
        		else if(rk>size[son[p][0]]+cnt[p]){
        			rk-=size[son[p][0]]+cnt[p];
        			p=son[p][1];
                }
                else	return p;
            }
        }
        inline void insert(int x){
            int f=0,cur=root;
            while(cur&&x!=val[cur]){
                f=cur;
                cur=son[cur][x>val[cur]];
            }
            if(cur) ++cnt[cur];
            else{
                cur=++ndnum;
                fa[cur]=f;
                son[cur][0]=son[cur][1]=0;
                size[cur]=cnt[cur]=1;
                val[cur]=x;
                if(f) son[f][x>val[f]]=cur;
            }
            splay(cur,0);
        }
        inline void reverse(int l,int r){
            l=search(l);r=search(r+2);
            splay(l,0);splay(r,l);
            tag[son[r][0]]^=1;//区间在左儿子,打上标记,标记两次等于没标记
        }
        inline void bianli(int p){
            pushdown(p);
            if(son[p][0]) bianli(son[p][0]);
            if(val[p]<n+2&&val[p]>1)
                printf("%d ",val[p]-1);
            if(son[p][1]) bianli(son[p][1]);
        }
    }Tree;
    int main()
    {
        read(n);read(m);
        for(ri i=1;i<=n+2;++i) Tree.insert(i);//前后个多插入一个数防爆,注意范围的移动
        for(ri i=1;i<=m;++i){
            int l,r;
            read(l);read(r);
            Tree.reverse(l,r);
        }
        Tree.bianli(Tree.root);
        return 0;
    }
    

    插入数到指定位置

    这里只讲插入单个数,对于一段数的插入,有点麻烦 笔者水平有限

    模板题

    给定一个序列,初始为空。现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置。每插入一个数字,我们都想知道此时最长上升子序列长度是多少?

    只讲插入操作,对于题目中求最长上升子序列请参考题解

    把一个数\(x\)插入到位置\(pos\):把排名为\(pos-1\)的数转到根结点,把排名为\(pos\)的数转到根结点的儿子,显然\(pos\)的左子树为空,把\(x\)变为它的左儿子,即可完成操作。

    代码实现:

    inline void ins(int x,int pos){
    	int l=search(pos-1),r=search(pos);
    	splay(l,0);
    	splay(r,l);
    	son[r][0]=++ndnum;
    	fa[ndnum]=r;
    	cnt[ndnum]=size[ndnum]=1;
    	val[ndnum]=x;
    	splay(ndnum,0);
    }
    
  • 相关阅读:
    linux中RabbitMQ安装教程
    linux中的文件权限chmod
    ceph架构简介
    利用双重检查锁定和CAS算法:解决并发下数据库的一致性问题
    对接第三方服务引起的小思考-回调和Sign算法
    <<Java并发编程的艺术>>-阅读笔记和思维导图
    SpringBoot2+Netty打造通俗简版RPC通信框架(升级版)
    SpringBoot2+Netty打造通俗简版RPC通信框架
    [安全] Kali Linux (debian)系统使用记录
    [安全] nmap工具的使用
  • 原文地址:https://www.cnblogs.com/hzyhome/p/11658222.html
Copyright © 2020-2023  润新知