• luogu P3369 【模板】普通平衡树 (替罪羊树


    周二lbg小捞弟讲了替罪羊树

    好啊!讲的好啊!再来一遍!

    替罪羊树真是一种优美的算法

    嗯对就是暴力

    分块也是暴力但是一点不优美

    emmm

    那我们从头开始讲起吧

    我们需要进行以下操作

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

    可以想到这是一棵树

    二叉搜索树

    即一棵节点的左儿子都比该节点小,右儿子都比该节点大的树

     (图片跑了

    但是如果插入的树是递增或递减或有一侧节点过多

    那就变成了一条链    显然一点也不优

     (自行脑补图片

    那么这种时候我们需要重新定义根节点使这棵树变得平衡

    顺其自然想到了暴力重构

    我们可以想到把这棵树拍扁,拎起最中间的节点做根节点,在递归拎下去

    自然会得到一棵平衡的新树

    当然你在代码里怎么可能拍扁一棵树

     emmm

    有一个东西叫做中序遍历

    就是一棵树,先遍历左节点再到根节点再到右节点

    这样遍历的结果跟把这棵树拍扁的效果一样w

     (想象上面你自己想象出来的图片的拍扁状态

    我们来看看代码吧

    首先是需要用的变量

    struct scapegoat{
      int sonl,sonr;//记录左右儿子编号
      int val,size,tot;//val是值,size是节点下的子树的实际大小,tot是删除前总共的大小
      int del;//标记是否被删除
    }e[N];
    int t[N],g[N];//t记录中序遍历的顺序,g。。。
    int pool,root,cnt;

    emmmm

    g是干啥的呢。。。

    网上题解解释如下

    动态分配内存的速度可以说是非常慢了,我们要手写一个内存池来分配空间

    emmmmm

    嗯完全不懂(溜

    关于替罪羊树的精华,即重构,又一个alpha因子

    如果左子树或右子树大于这棵树的重量的alpha倍,这棵树就该重构了

    经过一波理性分析

    alpha应该再0.5-1.0之间

    一般就取0.75辽w

    这是判断是否应该重构的函数

    bool isbad(int x){
        if((double)e[x].size * alpha <= (double)max(e[e[x].sonl].size,e[e[x].sonr].size)) 
            return true;
        return false;
    }

    然后如何重构

    首先我们要中序遍历一下

    void dfs(int now){
        if(!now)
          return;
        dfs(e[now].sonl);
        if(e[now].del)
          t[++cnt] = now;
        else g[++pool] = now;
        dfs(e[now].sonr);
    }

    然后再拎出来中间的点重构

    void rebuild(int &now){
        cnt = 0;
        dfs(now);
        if(cnt)
          build(1,cnt,now);
        else
          now = 0; 
    }

    需要注意的是,每次中序遍历这个cnt都要清零

    build函数:

    void build(int l,int r,int &now){
      int mid = (l + r) >> 1;
        now = t[mid];
        if(l == r){
            e[now].sonl = e[now].sonr = 0;
            e[now].size = e[now].tot = e[now].del = 1;
            return;
        }
        if(l < mid)
          build(l,mid - 1,e[now].sonl);
        else
          e[now].sonl=0;
        build(mid + 1,r,e[now].sonr);
        e[now].size = e[e[now].sonl].size + e[e[now].sonr].size + 1;
        e[now].tot = e[e[now].sonl].tot + e[e[now].sonr].tot + 1; 
    }

    然后就是题目要求的操作了

    我们再看一遍题面

    首先是插入操作

    小捞弟如是说:很遗憾,和二叉搜索树的操作没区别

    显然不一样

    在最后要加上判断是否需要重构

    void insert(int &now,int val){
        if(!now){
            now = g[pool--];
            e[now].val = val;
            e[now].sonl = e[now].sonr = 0;
            e[now].size = e[now].tot = e[now].del = 1; 
            return;
        }
        e[now].size++;
        e[now].tot++; 
        if(e[now].val >= val)
            insert(e[now].sonl,val);
        else
            insert(e[now].sonr,val);
        if(isbad(now))
          rebuild(now);
    }

    然后是删除

    删除的时候也要判断是否需要重构

    当左右子树中有一个删除的点数过多时显然也不平衡

    所以也要重构

    老板说超过50%会WA也不知道对不对懒得去试

    void delet(int &now,int k){
        if(e[now].del && e[e[now].sonl].size + 1 == k){
          e[now].del = 0;
          e[now].size--;
            return;
        }
        e[now].size--;
        if(e[e[now].sonl].size + e[now].del >= k){
            delet(e[now].sonl,k);
        } 
        else{
            delet(e[now].sonr,k - e[e[now].sonl].size - e[now].del);
        }
    }
    
    void delet_size(int k){
        delet(root,find_rank(k));
        if(e[root].tot * alpha >= e[root].size)
          rebuild(root);
    }

    再就是找数

    排名为k的数和第k个数的值

    int find_rank(int k){
        int now = root;
        int ans = 1;
        while(now){
            if(e[now].val >= k)
          now = e[now].sonl;
            else{
                ans += e[e[now].sonl].size + e[now].del;
                now = e[now].sonr;
            }
        } 
        return ans;
    }
    
    int find_kth(int k){
        int now = root;
        while(now){
            if(e[now].del && e[e[now].sonl].size + 1 == k)
                return e[now].val;
            else if(e[e[now].sonl].size >= k)
                    now = e[now].sonl;
            else{
                k -= e[e[now].sonl].size + e[now].del;
                now = e[now].sonr;
            } 
        }
        return e[now].val;
    }

    那么,基本操作就结束辽www

    成功的用题面和分开的代码占了很多版面假装写了很多

    下面是luoguP3369的正解代码

    #include<cstdio>
    #include<algorithm>
    #define sev en
    #define N 1000001
    #define alpha 0.75
    using namespace std;
    
    struct scapegoat{
      int sonl,sonr;
      int val,size,tot;
      int del;
    }e[N];
    int t[N],g[N];
    int pool,root,cnt;
    
    bool isbad(int x){
        if((double)e[x].size * alpha <= (double)max(e[e[x].sonl].size,e[e[x].sonr].size)) 
        return true;
        return false;
    }
    
    void dfs(int now){
        if(!now)
          return;
        dfs(e[now].sonl);
        if(e[now].del)
          t[++cnt] = now;
        else g[++pool] = now;
        dfs(e[now].sonr);
    }
    
    void build(int l,int r,int &now){
      int mid = (l + r) >> 1;
        now = t[mid];
        if(l == r){
            e[now].sonl = e[now].sonr = 0;
            e[now].size = e[now].tot = e[now].del = 1;
            return;
        }
        if(l < mid)
          build(l,mid - 1,e[now].sonl);
        else
          e[now].sonl=0;
        build(mid + 1,r,e[now].sonr);
        e[now].size = e[e[now].sonl].size + e[e[now].sonr].size + 1;
        e[now].tot = e[e[now].sonl].tot + e[e[now].sonr].tot + 1; 
    }
    
    void rebuild(int &now){
        cnt = 0;
        dfs(now);
        if(cnt)
          build(1,cnt,now);
        else
          now = 0; 
    }
    
    void insert(int &now,int val){
        if(!now){
            now = g[pool--];
            e[now].val = val;
            e[now].sonl = e[now].sonr = 0;
            e[now].size = e[now].tot = e[now].del = 1; 
            return;
        }
        e[now].size++;
        e[now].tot++; 
        if(e[now].val >= val)
            insert(e[now].sonl,val);
        else
            insert(e[now].sonr,val);
        if(isbad(now))
          rebuild(now);
    }
    
    int find_rank(int k){
        int now = root;
        int ans = 1;
        while(now){
            if(e[now].val >= k)
          now = e[now].sonl;
            else{
                ans += e[e[now].sonl].size + e[now].del;
                now = e[now].sonr;
            }
        } 
        return ans;
    }
    
    int find_kth(int k){
        int now = root;
        while(now){
            if(e[now].del && e[e[now].sonl].size + 1 == k)
                return e[now].val;
            else if(e[e[now].sonl].size >= k)
                    now = e[now].sonl;
            else{
                k -= e[e[now].sonl].size + e[now].del;
                now = e[now].sonr;
            } 
        }
        return e[now].val;
    }
    
    void delet(int &now,int k){
        if(e[now].del && e[e[now].sonl].size + 1 == k){
          e[now].del = 0;
          e[now].size--;
            return;
        }
        e[now].size--;
        if(e[e[now].sonl].size + e[now].del >= k){
            delet(e[now].sonl,k);
        } 
        else{
            delet(e[now].sonr,k - e[e[now].sonl].size - e[now].del);
        }
    }
    
    void delet_size(int k){
        delet(root,find_rank(k));
        if(e[root].tot * alpha >= e[root].size)
          rebuild(root);
    }
    
    int main(){
        int m;
        scanf("%d",&m);
        for(int i = N - 1;i >= 1;i--){
            g[++pool] = i;
        }
        while(m--){
            int op,x;
        scanf("%d%d",&op,&x);
            if(op == 1)
          insert(root,x);
            if(op == 2)
          delet_size(x);
            if(op == 3)
          printf("%d
    ",find_rank(x));
            if(op == 4)
          printf("%d
    ",find_kth(x));
            if(op == 5)
          printf("%d
    ",find_kth(find_rank(x) - 1));
            if(op == 6)
          printf("%d
    ",find_kth(find_rank(x + 1)));
        }
        return 0;
    }

    嗯结束啦

    咕咕咕这么久的blog难得更新居然写了这么长emmm

    希望想写的中国剩余定理能在下周写完qaq

    啊对本来想搞点图的但是emmmm你懂的【咕

  • 相关阅读:
    人民币格式化 ,分割
    解决IE下页面空白或者报错:[vuex] vuex requires a Promise polyfill in this browser
    js生成图片
    适用于iview的表格转Excel插件
    js金额转大写数字
    webstorm vue cli 热更新不起作用解决办法
    纯css实现 switch开关
    vue 时间戳转 YYYY-MM-DD h:m:s
    Simple2D-20(重构)
    Simple2D-19(音乐播放器)播放器的源码实现
  • 原文地址:https://www.cnblogs.com/sevenyuanluo/p/10497332.html
Copyright © 2020-2023  润新知