• 普通平衡树 lg3369


     在多次学习splay后,我终于理解并码出了整份代码

    参考了https://tiger0132.blog.luogu.org/slay-notes的博客

    具体实现原理在上面这篇博客和百度中可以查到,接下来我们看一下代码

    #include<bits/stdc++.h>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define ls st[p].ch[0]
    #define rs st[p].ch[1]
    inline int read(){
        int w=0,f=1;
        char ch=getchar();
        while(ch<'0'||ch>'9'){
            if(ch=='-') f=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            w=(w<<3)+(w<<1)+ch-48;
            ch=getchar();
        }
        return w*f;
    } 
    int n,m,cnt,tot,root;
    struct node{
        int val,cnt,sum,ch[2],f;
    }st[1000010];//val表示当前值,cnt表示出现次数,sum表示包括自己在内的子树大小,ch[0]为左儿子,ch[1]为右儿子
    inline void push_up(int x){
        st[x].sum=st[st[x].ch[0]].sum+st[st[x].ch[1]].sum+st[x].cnt;
    }//上推,子树大小为左子树加右子树加自身
    inline void connect(int x,int fa,int son){
        st[x].f=fa;st[fa].ch[son]=x;
    }//重新连接父子节点
    inline bool identify(int x){
        return st[st[x].f].ch[1]==x;
    }//判断自己是左儿子还是右儿子
    inline void rotate(int x){
        int y=st[x].f;int z=st[y].f;//父亲和曾祖父
        int yson=identify(x);int zson=identify(y);//身份认定
        int b=st[x].ch[yson^1];//往上连接的节点一定是st[x].ch[yson^1]可以手画几张图推一下情况
        connect(b,y,yson);connect(y,x,(yson^1));connect(x,z,zson);
        push_up(y);push_up(x);return;
    }//rotate的实现原理在上面那个博客里有详细介绍
    inline void splay(int x,int goal){
        while(st[x].f!=goal){
            int y=st[x].f;int z=st[y].f;
            int yson=identify(x);int zson=identify(y);
            if(z!=goal){
                if(yson==zson) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
        if(!goal) root=x;
        return;
    }//splay操作就是将节点向目标不断旋转
    inline void insert(int x){
        int now=root;int f=0;
        while(now&&st[now].val!=x){//原平衡树上不一定有现在查询的这个值,所以在往下跳的同时记录上一步,也就是父节点的位置
            f=now;
            now=st[now].ch[x>st[now].val];
        }
        if(now){
            st[now].cnt++;//如果查询到了,并且发现已经有这个节点,就cnt++;
        }
        else{
            tot++;now=tot;//如果发现原先没有这么个节点,就新建一个
            if(f){
                st[f].ch[x>st[f].val]=now;//从父亲连向儿子
            }
            st[now].ch[0]=st[now].ch[1]=0;//儿子连向父亲
            st[now].sum=st[now].cnt=1;
            st[now].f=f;st[now].val=x;
        }
        splay(now,0);return;//将当前节点旋转到根
    }
    inline void find(int x){
        int now=root;//本操作与上面的操作类似,找到当前值的节点,然后把其旋转到根上
        if(!now) return;
        while(st[now].ch[x>st[now].val]&&x!=st[now].val){
            now=st[now].ch[x>st[now].val];
        }
        splay(now,0);return;
    }
    inline int Next(int x,int f){//当f=0时查询的是前驱,f=1时查询的是后继
        find(x);int now=root;
        if(st[now].val<x&&!f) return now;
        if(st[now].val>x&&f) return now;
        now=st[now].ch[f];
        while(st[now].ch[f^1]) now=st[now].ch[f^1];
        return now;
    }
    inline void Delete(int x){//删除节点
        int la=Next(x,0);int ne=Next(x,1);//查询该点的前驱后继
        splay(la,0);splay(ne,la);//现将前驱转到根,再将后继转向前驱,此时后继的左儿子就是当前要删的节点了
        int now=st[ne].ch[0];
        if(st[now].cnt>1){//如果该点的cnt>1,cnt--即可,要不就把子节点的联系断掉就好了
            st[now].cnt--;
            splay(now,0);
        }
        else{
            st[ne].ch[0]=0;
        }
        return;
    }
    inline int k_th(int x){//查询第k大
        int now=root;
        if(st[now].sum<x) return false;//如果总数不够就大力返回FALSE;
        while(true){
            int lson=st[now].ch[0];//左儿子
            if(x>st[lson].sum+st[now].cnt){//如果x比左儿子的大小加上当前节点出现次数还大,就向右子树查询,记得减去之前的数值
                x-=st[lson].sum+st[now].cnt;
                now=st[now].ch[1];
            }
            else if(st[lson].sum>=x){
                now=lson;//如果x比左子树的大小小,就向左查询
            }
            else return st[now].val;//如果不属于以上两种情况就是在当前节点了
        }
    }
    int main(){//主函数就是根据题目要求大力操作了
        m=read();int i,j,k;root=0;
        insert(INF);insert(-INF);
        while(m--){
            int opt=read(),x=read();
            if(opt==1) insert(x);
            if(opt==2) Delete(x);
            if(opt==3){
                find(x);printf("%d
    ",st[st[root].ch[0]].sum);
            }
            if(opt==4){
                printf("%d
    ",k_th(x+1));
            }
            if(opt==5){
                printf("%d
    ",st[Next(x,0)].val);
            }
            if(opt==6){
                printf("%d
    ",st[Next(x,1)].val);
            }
        }
        return 0;
    }

    普通平衡树这道题大概就是这样了,接下来会跟进有关题目的训练及题解。

  • 相关阅读:
    最近有人说我欺骗消费者,今天来一波视频分享
    前端 Java Python等资源合集大放送
    dubbo源码学习(四):暴露服务的过程
    dubbo源码学习(二) : spring 自定义标签
    Dubbo多注册中心和Zookeeper服务的迁移
    线程各种状态转换分析
    java并发之同步辅助类CountDownLatch
    工作5年的Java程序员,才学会阅读源码,可悲吗?
    【阿里面试系列】Java线程的应用及挑战
    「阿里面试系列」搞懂并发编程,轻松应对80%的面试场景
  • 原文地址:https://www.cnblogs.com/wenci/p/10121605.html
Copyright © 2020-2023  润新知