前言部分

splay是个什么东西呢?

它就是个平衡树,支持以下操作

这些操作还可以用treap,替罪羊树,红黑树,multiset balabala(好像混进去什么奇怪的东西)

这里就只说一下splay(其他的窝不会)(splay窝也不会

先来几个变量和一些辅助函数:

root:当前平衡树的根是那个节点

sz:整个平衡树的大小

ch[x][0]:x的左儿子的编号

ch[x][1]:x的右儿子的编号

size[x]:x和它的子树的大小

cnt[x]:编号为x的点的权值出现了几次

par[x]:x的父亲的编号

key[x]:编号为x的点的权值

get(x):查询x是它父亲的左儿子还是右儿子(左儿子返回0,右儿子返回1)

pushup(x):统计x的size和cnt

clear(x):将x的ch,size,cnt,key清0(删除用)

代码:

  1. void pushup(int x)
  2. {
  3. size[x]=size[ch[x][]]+size[ch[x][]]+cnt[x];//左子树+右子树+自身的cnt
  4. return ;
  5. }
  6.  
  7. void clear(int x)
  8. {
  9. size[x]=;ch[x][]=;ch[x][]=;key[x]=;cnt[x]=;par[x]=;
  10. return ;//全部清0就好辣
  11. }
  12.  
  13. bool get(int x)
  14. {
  15. return ((ch[par[x]][]==x)?:);//如果x是自己父亲的右儿子,就返回1,否则返回0
  16. }

正文开始

一.一些基础操作

说了辣么多,还没有介绍平衡树到底是个什么东西

不过不着急,我们先来看看二叉搜索树

二叉搜索树有一个神奇的性质:所有比当前节点x权值小的节点,都在x的左子树里面,权值比x大的点都在x的右子树里

有了这个神奇的性质,在一般情况下树高就是log级别的,查询的复杂度也就降到了log级别,这很好对不对?

但是duliu出题人总是会种出一些歪七扭八的二叉搜索树,就像下面这样(节点里面的数是权值)

对于第二种毒瘤的二叉搜索树,复杂度就退化成了O(n),这不优美。我们想让它成长,让它学会自己平衡。于是,它成长成了平衡树。

splay如何做到自己平衡?

我们看这个丑陋的树:

转一转(右旋)

我们发现,B原本的右儿子成了A的左儿子,B现在的右儿子是A,其余不变

为什么这样转?

我们把B转成了根,那么B就没有父亲了,但是多了A这个右儿子,需要处理成二叉。同时A失去了左儿子,并且A发现B还有一个原来的右儿子F,F比A小,于是A就让F当自己的左儿子

如果我们把A左旋(然后这个树就更不平衡了ρωρ)

我们发现A没有右儿子了,D的左儿子变成了A

旋转的方式和右旋差不多,这里是让D的左儿子认A当爹,让A认D当爹。

不过可惜D并没有左儿子,于是A转完了也就没有右儿子辣。

我们可以总结出旋转的规律:

现在我们要把x转到根的位置(不管现在x在哪)

我们用k表示x是它父亲的左儿子还是右儿子(0是左儿子,1是右儿子),y是x的儿子

就让y的k方向的儿子变成x的与k相反方向的儿子,y认x为爹,同时让y的父亲在y这个方向上的儿子替换成x

说人话版本:

y=par[x],z=par[y],k=get(x),e=get(y)

ch[y][k]=ch[x][k^1]

par[ch[y][k]]=y

par[y]=x

ch[x][k^1]=y

par[x]=z

ch[z][e]=x

代码版本:

  1. void rotate(int x)
  2. {
  3. int y=par[x],z=par[y],k=get(x),e=get(y);
  4. ch[y][k]=ch[x][k^];par[ch[y][k]]=y;
  5. par[y]=x;ch[x][k^]=y;
  6. par[x]=z;
  7. if(z)
  8. ch[z][e]=x;
  9. pushup(y);pushup(x);//记得pushup回去(统计信息)
  10. }

当然只旋转一次是肯定不够的,所以我们再来一个splay函数。

splay(x)就是把x旋转到根(当然也可以再带一个参数,让x旋转到那个参数去)

定义fa,让fa一直等于当前x的父亲,一直旋转x,直到x的父亲是0(x是根节点)

注意:如果x,x的父亲,x的爷爷在同一条线上,就先转x的父亲

  1. void splay(int x)
  2. {
  3. for(int fa;fa=par[x];rotate(x))
  4. if(par[fa])
  5. rotate((get(x)==get(fa))?fa:x);
  6. root=x;
  7. }

好像有什么不对的???

貌似这些操作一个都没有实现ρωρ

那就开始讲这些操作好了

二.平衡树支持的操作

1.插入X

如果当前平衡树里面没有元素,就直接sz++,root=sz

如果当前点的权值>x,就到左子树找x,反之则到右子树找,直到找到x,然后更新cnt,size,顺便再splay一下

如果找到最后都没有x,就说明x先前不在平衡树里,就新建一个节点,其权值为x,维护cnt,size

  1. void insert(int x)
  2. {
  3. if(root==)
  4. {
  5. sz++;
  6. key[sz]=x;
  7. root=sz;
  8. cnt[sz]=;size[sz]=;
  9. par[sz]=ch[sz][]=ch[sz][]=;
  10. return;//这里由于只有一个节点,就不需要splay了
  11. }
  12. int now=root,fa=;
  13. while()
  14. {
  15. if(key[now]==x)
  16. {
  17. cnt[now]++;
  18. pushup(now);//先更新now,再更新fa
  19. pushup(fa);
  20. splay(now); //为了以后方便,我们要把当前点splay到根
  21. return ;
  22. }
  23. fa=now;
  24. now=ch[now][key[now]<x];//第二维表示的是如果key[now]<x,就返回1,否则是0
  25. if(now==)//最终没有找着(不存在的节点默认值是0)
  26. {
  27. sz++;
  28. size[sz]=;cnt[sz]=;
  29. root=sz;
  30. key[sz]=x;
  31. ch[fa][key[fa]<x]=sz;
  32. par[sz]=fa;
  33. key[sz]=x;
  34. pushup(fa);
  35. splay(sz);return ;
  36. }
  37. }
  38. }

插入

删除比较麻烦待会再说

2.查询x的排名

还是遵循key[now]比x小就往左子树找,否则就往右子树找的原则,直到找到

注意这里排名的定义是所有比x小的数的数量+1,如果有一个数q比x小,但是q出现了多次,那么重复出现的q也算作比x小的数(就是答案也包括那些重复出现的数辣)。

因此,往左子树搜,当前答案不变,往右子树搜,答案增加size[左子树]

因为重复的也算,所以在now转移前还要加cnt[now]

查到了要记得splay!!!

  1. int findk(int x)
  2. {
  3. int rtn=;
  4. int now=root;
  5. while()
  6. {
  7. if(key[now]>x)now=ch[now][];
  8. else
  9. {
  10. rtn+=(ch[now][]?size[ch[now][]]:);//先加(不管是否找到)
  11. if(x==key[now])
  12. {
  13. splay(now);//记住splay
  14. return rtn+;//记住+1
  15. }
  16. rtn+=cnt[now];
  17. now=ch[now][];
  18. }
  19. }
  20. }

查询x的排名

3.查询第k大的数

k边查边减

如果当前点的左子树的size>=k,则去左子树查,k-=size[左子树]

否则就去右子树查,当now的size[左子树]+cnt[now]>=k的时候就说明now的权值就是第k大的数

因为如果当前的now不是第k大的话,就会去左子树查,但是现在已经是在右子树查的阶段了

  1. int kth(int x)
  2. {
  3. int now=root,rtn=;
  4. while()
  5. {
  6. if(ch[now][]&&size[ch[now][]]>=x)now=ch[now][];
  7. else
  8. {
  9. int temp=size[ch[now][]]+cnt[now];
  10. if(temp>=x)
  11. {
  12. return key[now];//这回不用splay了
  13. }
  14. x-=temp;now=ch[now][];
  15. }
  16. }
  17. }

kth

4.找x的前驱/后继

我们在找前驱/后继之前先把x插入原平衡树种,找完以后再删掉,会很方便。

在插入中,我们已经把x splay到根了,所以我们在找前驱的时候,就从根的左儿子开始,一路找到左儿子右子树的叶子节点,就是前驱。

后继:从根的右儿子开始,一路找到右儿子的左子树的叶子节点即可

  1. int pre()
  2. {
  3. int now=ch[root][];
  4. while(ch[now][])now=ch[now][];
  5. return now;
  6. }
  7. int next()
  8. {
  9. int now=ch[root][];
  10. while(ch[now][])now=ch[now][];
  11. return now;
  12. }

找前驱/后继

5.最复杂的删除X

情况比较多,咱慢慢来

先来一次findk(x),目的是把x旋到根(也可以直接splay(x))

1:x曾经出现过多次

对于这种情况,直接cnt--,size--

2:x只出现过1次

①:x没有儿子:直接clear(root)

②:x只有左儿子或者右儿子

让x唯一的儿子当根,并调整父子关系(x的儿子没有父亲神马的),clear(x)

③:最麻烦的情况,x两个儿子都有

把x的前驱转到根,让x的前驱继承x的右儿子(至于左儿子,在把x的前驱转到根的时候已经继承了),调整父子关系(x的右儿子认x的前驱神马的),clear(x)

  1. void del(int x)
  2. {
  3. int sy=fink(x);
  4. if(cnt[root]>)
  5. {
  6. cnt[root]--;pushup(root);return;
  7. }
  8. if(!ch[root][]&&!ch[root][])
  9. {
  10. clear(root);root=;return ;
  11. }
  12. if(!ch[root][])
  13. {
  14. int rt=root;root=ch[root][];par[root]=;clear(rt);return ;
  15. }
  16. else if(!ch[root][])
  17. {
  18. int rt=root;root=ch[root][];par[root]=;clear(rt);return ;
  19. }
  20. int rt=root;int pr=pre();
  21. splay(pr);
  22. ch[root][]=ch[rt][];
  23. par[ch[rt][]]=root;
  24. clear(rt);
  25. pushup(root);
  26. }

删除x

模板题:洛谷P3369普通平衡树

更难一点的窝不会的文艺平衡树

  1. #include<bits/stdc++.h>
  2. #define pa pair<int,int>
  3. using namespace std;
  4. inline int read()
  5. {
  6. char ch=getchar();
  7. int x=;bool f=;
  8. while(ch<''||ch>'')
  9. {
  10. if(ch=='-')f=;
  11. ch=getchar();
  12. }
  13. while(ch>=''&&ch<='')
  14. {
  15. x=(x<<)+(x<<)+(ch^);
  16. ch=getchar();
  17. }
  18. return f?-x:x;
  19. }
  20. const int N=;
  21. int n,sz;
  22. int ch[N][],par[N],key[N],cnt[N],size[N];
  23. int root;
  24. void pushup(int x)
  25. {
  26. size[x]=size[ch[x][]]+size[ch[x][]]+cnt[x];
  27. return ;
  28. }
  29. void clear(int x)
  30. {
  31. size[x]=;ch[x][]=;ch[x][]=;key[x]=;cnt[x]=;par[x]=;
  32. return ;
  33. }
  34. bool get(int x)
  35. {
  36. return ((ch[par[x]][]==x)?:);
  37. }
  38. void rotate(int x)
  39. {
  40. int y=par[x],z=par[y],k=get(x),e=get(y);
  41. ch[y][k]=ch[x][k^];par[ch[y][k]]=y;
  42. par[y]=x;ch[x][k^]=y;
  43. par[x]=z;
  44. if(z)
  45. ch[z][e]=x;
  46. pushup(y);pushup(x);
  47. }
  48. void splay(int x)
  49. {
  50. for(int fa;fa=par[x];rotate(x))
  51. if(par[fa])
  52. rotate((get(x)==get(fa))?fa:x);
  53.  
  54. root=x;
  55. }
  56. void insert(int x)
  57. {
  58. if(root==)
  59. {
  60. sz++;
  61. key[sz]=x;
  62. root=sz;
  63. cnt[sz]=;size[sz]=;
  64. par[sz]=ch[sz][]=ch[sz][]=;
  65. return;
  66. }
  67. int now=root,fa=;
  68. while()
  69. {
  70. if(key[now]==x)
  71. {
  72. cnt[now]++;
  73. pushup(now);
  74. pushup(fa);
  75. splay(now);//当前节点旋转
  76. return ;
  77. }
  78. fa=now;
  79. now=ch[now][key[now]<x];
  80. if(now==)
  81. {
  82. sz++;
  83. size[sz]=;cnt[sz]=;
  84. root=sz;
  85. key[sz]=x;
  86. ch[fa][key[fa]<x]=sz;
  87. par[sz]=fa;
  88. key[sz]=x;
  89. pushup(fa);
  90. splay(sz);return ;
  91. }
  92. }
  93. }
  94. int fink(int x)
  95. {
  96. int rtn=;
  97. int now=root;
  98. while()
  99. {
  100. if(key[now]>x)now=ch[now][];
  101. else
  102. {
  103. rtn+=(ch[now][]?size[ch[now][]]:);
  104. if(x==key[now])
  105. {
  106. splay(now);
  107. return rtn+;
  108. }
  109. rtn+=cnt[now];
  110. now=ch[now][];
  111. }
  112. }
  113. }
  114. int kth(int x)
  115. {
  116. int now=root,rtn=;
  117. while()
  118. {
  119. if(ch[now][]&&size[ch[now][]]>=x)now=ch[now][];
  120. else
  121. {
  122. int temp=size[ch[now][]]+cnt[now];
  123. if(temp>=x)
  124. {
  125. return key[now];
  126. }
  127. x-=temp;now=ch[now][];
  128. }
  129. }
  130. }
  131. int pre()
  132. {
  133. int now=ch[root][];
  134. while(ch[now][])now=ch[now][];
  135. return now;
  136. }
  137. int next()
  138. {
  139. int now=ch[root][];
  140. while(ch[now][])now=ch[now][];
  141. return now;
  142. }
  143. void del(int x)
  144. {
  145. int sy=fink(x);
  146. if(cnt[root]>)
  147. {
  148. cnt[root]--;pushup(root);return;
  149. }
  150. if(!ch[root][]&&!ch[root][])
  151. {
  152. clear(root);root=;return ;
  153. }
  154. if(!ch[root][])
  155. {
  156. int rt=root;root=ch[root][];par[root]=;clear(rt);return ;
  157. }
  158. else if(!ch[root][])
  159. {
  160. int rt=root;root=ch[root][];par[root]=;clear(rt);return ;
  161. }
  162. int rt=root;int pr=pre();
  163. splay(pr);
  164. ch[root][]=ch[rt][];
  165. par[ch[rt][]]=root;
  166. clear(rt);
  167. pushup(root);
  168. }
  169. int main()
  170. {
  171. n=read();
  172. for(int i=;i<=n;i++)
  173. {
  174. int opt=read(),x=read();;
  175. if(opt==)insert(x);
  176. if(opt==)del(x);
  177. if(opt==)printf("%d\n",fink(x));
  178. if(opt==)printf("%d\n",kth(x));
  179. if(opt==){insert(x);printf("%d\n",key[pre()]);del(x);}
  180. if(opt==){insert(x);printf("%d\n",key[next()]);del(x);}
  181. }
  182. }

这是P3369的代码ρωρ

蒟蒻的splay 1---------洛谷板子题普通平衡树的更多相关文章

  1. 洛谷P3380 二逼平衡树

    线段树+平衡树 我!又!被!卡!常!了! 以前的splay偷懒的删除找前驱后继的办法被卡了QAQ 放一个在洛谷开O2才能过的代码..我太菜了.. #include <bits/stdc++.h& ...

  2. 洛谷.3369.[模板]普通平衡树(Splay)

    题目链接 第一次写(2017.11.7): #include<cstdio> #include<cctype> using namespace std; const int N ...

  3. 洛谷.3391.[模板]文艺平衡树(Splay)

    题目链接 //注意建树 #include<cstdio> #include<algorithm> const int N=1e5+5; //using std::swap; i ...

  4. 【Splay】洛谷3372 【模板】线段树 1

    Splay区间加,询问区间和. #include<cstdio> #include<iostream> #include<cstring> #include< ...

  5. 【洛谷P3391】文艺平衡树——Splay学习笔记(二)

    题目链接 Splay基础操作 \(Splay\)上的区间翻转 首先,这里的\(Splay\)维护的是一个序列的顺序,每个结点即为序列中的一个数,序列的顺序即为\(Splay\)的中序遍历 那么如何实现 ...

  6. 【洛谷P3369】普通平衡树——Splay学习笔记(一)

    二叉搜索树(二叉排序树) 概念:一棵树,若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值: 它的左.右子树也分别为二叉搜索树 ...

  7. splay 模板 洛谷3369

    题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入 xx 数 删除 xx 数(若有多个相同的数,因只删除一个) 查询 xx 数的排名(排名定义为比当前数小的数 ...

  8. 洛谷水题p1421小玉买文具题解

    题目描述 班主任给小玉一个任务,到文具店里买尽量多的签字笔.已知一只签字笔的价格是1元9角,而班主任给小玉的钱是a元b角,小玉想知道,她最多能买多少只签字笔呢. 输入输出格式 输入格式: 输入的数据, ...

  9. 洛谷.3369.[模板]普通平衡树(fhq Treap)

    题目链接 第一次(2017.12.24): #include<cstdio> #include<cctype> #include<algorithm> //#def ...

随机推荐

  1. 从汇编到C

    一. 设置栈 1.1. C语言运行时需要和栈的意义 1.1.1. “C语言运行时(runtime)”需要一定的条件,这些条件由汇编来提供.C语言运行时主要是需要栈 1.1.2. C语言与栈的关系 a. ...

  2. 剑指offer-python-回溯法-矩阵中的路径

    这个系列主要详细记录代码详解的过程. 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格 ...

  3. nodeJS打包安装和问题处理

    一,执行步骤,打包报错 1,查看npm版本npm -v 2,查看gulp版本(报错怎么证明没安装)gulp --version 3,安装gulpnpm install --global gulp-cl ...

  4. flutter 布局简介

    import 'package:flutter/material.dart'; class LayoutDemo extends StatelessWidget { @override Widget ...

  5. php内核之数组

    1.zend_hash_num_elements 获取数组元素个数.宏定义如下: #define zend_hash_num_elements(ht) \ (ht)->nNumOfElement ...

  6. vue-element添加修改密码弹窗

    1.新建修改密码vue文件CgPwd.vue 代码如下: <template> <!-- 修改密码界面 --> <el-dialog :title="$t('c ...

  7. windows10安装pycharm,以及pycharm教程和破解码

    pycharm下载请点我 根据自己的情况选择安装目录 下面我们选择"64位安装"(根据自己的系统来选择),并勾上".py",如图所示: 一定要拉到最后才行  p ...

  8. iOS之UIDatePicker

    这个还要取决于手机系统设置

  9. 进阶:python3实现 插入排序

    一图胜千言,插入排序的核心逻辑如下: 将数据分成两半,前一半是已排好序的,后一半是待排序的 每次取后一半中的第一个数,在已排序的一半中,逆序依次比较,找到要插入的位置 记录插入的位置,在最后判断是否需 ...

  10. 解决json不能解析换行问题

    今天遇到一个问题,当我读取数据库中某条带换行的数据时,解析错误. 解决方法是在存入数据库时对数据做处理,把换行换成其他字符.代码如下: remark = remark.replace(/\n/g,&q ...