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\n",find_rank(x));
        if(op == 4)
      printf("%d\n",find_kth(x));
        if(op == 5)
      printf("%d\n",find_kth(find_rank(x) - 1));
        if(op == 6)
      printf("%d\n",find_kth(find_rank(x + 1)));
    }
    return 0;
}

嗯结束啦

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

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

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

 

posted @ 2019-03-10 17:05  ./seven  阅读(201)  评论(0编辑  收藏  举报