luogu P3369 【模板】普通平衡树 (替罪羊树
周二lbg小捞弟讲了替罪羊树
好啊!讲的好啊!再来一遍!
替罪羊树真是一种优美的算法
嗯对就是暴力
(分块也是暴力但是一点不优美
emmm
那我们从头开始讲起吧
我们需要进行以下操作
- 插入xx数
- 删除xx数(若有多个相同的数,因只删除一个)
- 查询xx数的排名(排名定义为比当前数小的数的个数+1+1。若有多个相同的数,因输出最小的排名)
- 查询排名为xx的数
- 求xx的前驱(前驱定义为小于xx,且最大的数)
- 求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你懂的【咕