平衡树的广阔天地中,以Treap,Splay等为代表的通过旋转来维护平衡的文艺平衡树占了觉大部分。

然而,今天我们要讲的Scapegoat Tree(替罪羊树)就是一个特立独行的平衡树,它通过暴力重构来维护平衡,并且凭借着好写,好调,常数小等特点十分有用。

记得g1n0st说过:

暴力即是优雅。

当然这里说的暴力并不是指那种不加以思考的无脑的暴力,而是说用繁琐而技巧性的工作可以实现的事,我用看似简单的思想和方法,也可以达到近似于前者的空间复杂度和时间复杂度,甚至可以更优,而其中也或多或少的夹杂着一些" Less is more "的思想在其中。

而替罪羊树的重构就充满了暴力美学,一言不合就把整棵子树拍扁重建,比如一棵这样的树:

而这样很显然,根的右子树(以\(11\)为根)的子树太深了,我们给它手动拍扁:

然后接回去就变成了:

至于如何有序,我们想一下二叉树的中序遍历,不是可以直接用线性时间把那个拍扁后的序列得出来了。

然后我们发现重构虽然可以维持树的形状,但它本身的较大的复杂度开销也会引起TLE,因此我们要控制重构的次数

我们引入一个平衡因子\(alpha\),一般取值在\([0,7,0.8]\)之间,当一棵子树的左右子树的节点个数的较大值大于这棵子树总的节点个数\(\cdot alpha\)时,我们就把这棵子树拍扁重构。

特殊地,当一个点被插入时,如果有多个要被重建的节点,那们我们就把以最高的(深度最小的)节点(又叫替罪羊节点)为根的整棵子树重构即可。

形象的理解一下:子树要被重建不是我原来根的锅,但是我就是被拍扁了还被重建了。果然不负替罪羊树的称号。

然后在删除时,我们如果直接删除由于没有旋转操作,大量的重构可能会引起TLE。

因此我们像线段树的lazy标记一样,在删除一个点时直接打标记删除即可。

然后又是板子题的CODE

  1. #include<cstdio>
  2. #include<cctype>
  3. using namespace std;
  4. typedef double DB;
  5. const int N=100005;
  6. const DB alpha=0.75;
  7. struct Scapegoat
  8. {
  9. int ch[2],size,fac,val;
  10. bool exi;
  11. }node[N];//size是子树总大小(算上被删除的点),fac是实际上子树总大小(不计被删除的点),exi表示是否被删除
  12. int cur[N],mempol[N],cnt,tot,n,opt,x,rt,st;
  13. inline char tc(void)
  14. {
  15. static char fl[100000],*A=fl,*B=fl;
  16. return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
  17. }
  18. inline void read(int &x)
  19. {
  20. x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
  21. while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag;
  22. }
  23. inline void write(int x)
  24. {
  25. if (x<0) putchar('-'),x=-x;
  26. if (x>9) write(x/10);
  27. putchar(x%10+'0');
  28. }
  29. inline int max(int a,int b)
  30. {
  31. return a>b?a:b;
  32. }
  33. inline bool balance(int now)//判断是否平衡
  34. {
  35. return (DB)node[now].fac*alpha>(DB)max(node[node[now].ch[0]].fac,node[node[now].ch[1]].fac);
  36. }
  37. inline void pushup(int now)
  38. {
  39. node[now].size=node[node[now].ch[0]].size+node[node[now].ch[1]].size+1;
  40. node[now].fac=node[node[now].ch[0]].fac+node[node[now].ch[1]].fac+1;
  41. }
  42. inline void build(int now)
  43. {
  44. node[now].ch[0]=node[now].ch[1]=0;
  45. node[now].size=node[now].fac=1;
  46. }
  47. inline void traversal(int now)//中序遍历
  48. {
  49. if (!now) return; traversal(node[now].ch[0]);
  50. if (node[now].exi) cur[++cnt]=now; else mempol[++tot]=now;
  51. traversal(node[now].ch[1]);
  52. }
  53. inline void setup(int l,int r,int &now)//将被拍扁的序列接成一棵树,注意每次取端点保证深度最小
  54. {
  55. int mid=l+r>>1; now=cur[mid];
  56. if (l==r) { build(now); return; }
  57. if (l<mid) setup(l,mid-1,node[now].ch[0]); else node[now].ch[0]=0;
  58. setup(mid+1,r,node[now].ch[1]); pushup(now);
  59. }
  60. inline void rebuild(int &now)//重构
  61. {
  62. cnt=0; traversal(now);
  63. if (cnt) setup(1,cnt,now); else now=0;
  64. }
  65. inline void insert(int &now,int val)//插入,还是遵循BST的性质
  66. {
  67. if (!now)
  68. {
  69. now=mempol[tot--]; node[now].val=val; node[now].exi=1;
  70. build(now); return;
  71. }
  72. ++node[now].size; ++node[now].fac;
  73. if (val<=node[now].val) insert(node[now].ch[0],val); else insert(node[now].ch[1],val);
  74. }
  75. inline void check(int now,int val)//在插入时检查合法性,一言不和就重构
  76. {
  77. int d=val<=node[now].val?0:1;
  78. while (node[now].ch[d])
  79. {
  80. if (!balance(node[now].ch[d])) { rebuild(node[now].ch[d]); break; }
  81. now=node[now].ch[d]; d=val<=node[now].val?0:1;
  82. }
  83. }
  84. inline int get_rk(int val)//得到排名,由于和许多的BST类似,就不再赘述
  85. {
  86. int now=rt,rk=1;
  87. while (now)
  88. {
  89. if (val<=node[now].val) now=node[now].ch[0];
  90. else rk+=node[node[now].ch[0]].fac+node[now].exi,now=node[now].ch[1];
  91. }
  92. return rk;
  93. }
  94. inline int get_val(int rk)//得到排名为rk的数
  95. {
  96. int now=rt;
  97. while (now)
  98. {
  99. if (node[now].exi&&node[node[now].ch[0]].fac+1==rk) return node[now].val;
  100. else if (node[node[now].ch[0]].fac>=rk) now=node[now].ch[0];
  101. else rk-=node[node[now].ch[0]].fac+node[now].exi,now=node[now].ch[1];
  102. }
  103. }
  104. inline void remove_rk(int &now,int rk)//删除排名为rk的数
  105. {
  106. if (node[now].exi&&node[node[now].ch[0]].fac+1==rk) { node[now].exi=0; --node[now].fac; return; }
  107. --node[now].fac; if (node[node[now].ch[0]].fac+node[now].exi>=rk) remove_rk(node[now].ch[0],rk);
  108. else remove_rk(node[now].ch[1],rk-node[node[now].ch[0]].fac-node[now].exi);
  109. }
  110. inline void remove_val(int val)//删除值为val的数,注意如果实际上的点已经很少了也要重构
  111. {
  112. remove_rk(rt,get_rk(val));
  113. if ((double)node[rt].size*alpha>node[rt].fac) rebuild(rt);
  114. }
  115. inline void init(void)
  116. {
  117. for (register int i=100000;i>=1;--i)
  118. mempol[++tot]=i;
  119. }
  120. int main()
  121. {
  122. //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
  123. read(n); init();
  124. while (n--)
  125. {
  126. read(opt); read(x);
  127. switch (opt)
  128. {
  129. case 1:st=rt,insert(rt,x),check(st,x); break;
  130. case 2:remove_val(x); break;
  131. case 3:write(get_rk(x)),putchar('\n'); break;
  132. case 4:write(get_val(x)),putchar('\n'); break;
  133. case 5:write(get_val(get_rk(x)-1)),putchar('\n'); break;
  134. case 6:write(get_val(get_rk(x+1))),putchar('\n'); break;
  135. }
  136. }
  137. return 0;
  138. }

——识替罪羊树之算法乃吾生之幸也!

在平衡树的海洋中畅游(二)——Scapegoat Tree的更多相关文章

  1. 在平衡树的海洋中畅游(一)——Treap

    记得有一天翔哥毒奶我们: 当你们已经在平衡树的海洋中畅游时,我还在线段树的泥沼中挣扎. 我觉得其实像我这种对平衡树一无所知的蒟蒻也要开一开数据结构了. 然后花了一天啃了下最简单的平衡树Treap,感觉 ...

  2. 在平衡树的海洋中畅游(三)——Splay

    Preface 由于我怕学习了Splay之后不直接写blog第二天就忘了,所以强行加了一波优先级. 论谁是天下最秀平衡树,我Splay第一个不服.维护平衡只靠旋转. 一言不合转死你 由于平衡树我也介绍 ...

  3. 在平衡树的海洋中畅游(四)——FHQ Treap

    Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...

  4. 【BZOJ5020】【THUWC2017】在美妙的数学王国中畅游(Link-Cut Tree,组合数学)

    [BZOJ5020][THUWC2017]在美妙的数学王国中畅游(Link-Cut Tree,组合数学) 题解 Description 数字和数学规律主宰着这个世界. 机器的运转, 生命的消长, 宇宙 ...

  5. 简析平衡树(一)——替罪羊树 Scapegoat Tree

    前言 平衡树在我的心目中,一直都是一个很高深莫测的数据结构.不过,由于最近做的题目的题解中经常出现"平衡树"这三个字,我决定从最简单的替罪羊树开始,好好学习平衡树. 简介 替罪羊树 ...

  6. [LOJ2289][THUWC2017]在美妙的数学王国中畅游:Link-Cut Tree+泰勒展开

    分析 又有毒瘤出题人把数学题出在树上了. 根据泰勒展开,有: \[e^x=1+\frac{1}{1!}x+\frac{1}{2!}x^2+\frac{1}{3!}x^3+...\] \[sin(x)= ...

  7. 平衡树 替罪羊树(Scapegoat Tree)

    替罪羊树(Scapegoat Tree) 入门模板题 洛谷oj P3369 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入xx数 删除xx数(若有多个相同 ...

  8. 由生到死10个月!做App中的“二”有多难

    十月,原本是怀胎过程的喜悦时光,但这段个时光,如今却是绝大多数App从生到死的所有时间.在App市场表面形式一片大好,彻底主宰我们生活.工作.娱乐的当下,绝大多数用户只是在App海洋中只取一瓢饮,其他 ...

  9. FastReport 中添加二维码功能.(Delphi)

    http://www.cnblogs.com/fancycloud/archive/2011/07/24/2115240.html FastReport 中添加二维码功能.(Delphi)   在实际 ...

随机推荐

  1. 安卓开发之Room实体定义数据

    使用Room实体定义数据 在Room库中,entities(实体)代表着相关字段集.每一个entity(实体)代表着相关联数据库中的一个表.entity 类必须通过Database 类中的entiti ...

  2. (后台)详细了解java中的null(转)

    转自CSDN: 相信大家对于NullPointException 这个让人又爱又恨的不陌生吧..对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常(NPE)的骚扰 .今天我们就来谈 ...

  3. 洗礼灵魂,修炼python(25)--自定义函数(6)—从匿名函数进阶话题讲解中解析“函数式编程”

    匿名函数进阶 前一章已经说了匿名函数,匿名函数还可以和其他内置函数结合使用 1.map map():映射器,映射 list(map(lambda x:x*2,range(10))) #把range产生 ...

  4. Linux系统将http转为https

    想把网站由http访问转变为https访问并没有想象中那么难,网上查了一些资料,想要转为https需要SSL安全证书,这里推荐一款景安网络的证书,可以免费试用一年时间,自己拿来实践还是很不错的选择. ...

  5. zookeeper-03 命令行操作

    1. 前言 在3台机器分别部署了zookeeper-3.4.5,本文操作是在此基础上进行的.部署详情参见上一篇文章 2. 客户端登录与帮助查看 # 由于是集群模式,所以可以在3台机器的其中任意一台进行 ...

  6. Linux 小知识翻译 - 「虚拟化技术」

    这次聊聊「虚拟化技术」. 虚拟化技术,有时简称为「虚拟化」,最近经常听人说它.但是却不太清楚它的意思.到底虚拟了什么东西?本来是用来干什么的? 有名的虚拟化软件要数 VMware 和 VirtualB ...

  7. if语句的嵌套以及条件运算符和条件表达式(初学者)

    1.当if语句中的执行语句又是if语句时,则构成了if语句的嵌套情形. 其一般形式可表示为: if() { if()……; } 或: if() if()语句1: else 语句2: else if() ...

  8. Properties集合_list方法与store方法

    Properties集合和流对象结合的功能 list()方法: import java.util.Properties; public class PropertiesDemo { public st ...

  9. 函数中声明变量不用Var的情况

    我们都知道函数中声明变量不用Var时这个变量会成为全局变量,但是并不是函数一开始执行就会把它变为全局变量,必须执行到这条语句. 看一段代码 function f(){    alert(a);    ...

  10. Ubuntu18.04安装Tensorflow+cuda+cuDNN

    本文写的比较简单,期间遇到的一些小麻烦,自己不认为成为阻碍,所以没有详细写. 如有疑问可以联系QQ:2922530320 Pycharm Pycharm使用Anaconda Pycharm 在新建项目 ...