2021.12.06 平衡树——Treap
2021.12.06 平衡树——Treap
https://www.luogu.com.cn/blog/HOJQVFNA/qian-xi-treap-ping-heng-shu
1.二叉搜索树
1.1 性质
左子树的值都小于父节点,右子树的值都大于父节点。
2.堆
2.1 性质
是一颗完全二叉树,并且子节点不大于或小于父节点
3.Treap
一棵Treap上的节点有关键码和优先级。关键码满足二叉树的性质,优先级满足堆的性质。关键码就是真实的值,优先级 rand() 一个就成。
3.1 旋转rotate
为了满足关键码和优先级的顺序不变,也就是为了维护二叉搜索树和堆的性质,有大佬搞出了rotate这一操作。
3.1.1 右旋
就是把grandpa下降到右边,dad升到根节点。
旋转前:

旋转后:

3.1.2 左旋
就是把grandpa下降到左边,uncle升到根节点。
旋转前:

旋转后:

3.1.3 代码实现
我们可以发现每种旋转中只有两个节点的父节点变了。
如果是右旋,改变了cousin与grandpa:cousin是dad的右儿子,变为consin是grandpa的左儿子;dad的右儿子变成了grandpa。
如果是左旋,改变了son与grandpa:son是uncle的左儿子,变成了son是grandpa的右儿子;uncle的左儿子变成了grandpa。
改变的节点都与旋转的方向有关,所以左旋和右旋可以合并,写在一个函数中。
flag :0 \(\longrightarrow\) 左旋,1 \(\longrightarrow\) 右旋
son[x][0/1] :0 \(\longrightarrow\) 左儿子,1 \(\longrightarrow\) 右儿子
代码如下:
inline void rotate(int &x,int flag){
int y=son[x][flag^1];
int change=son[y][flag];
son[x][flag^1]=change;
son[y][flag]=x;
update(x);
update(y);
x=y;
}
旋转完之后因为节点关系变化,记得更新dad和grandpa。
sizei[x] :x节点为根节点所在子树大小
same[x] :与x节点关键码大小相同的个数
代码如下:
inline void update(int x){
sizei[x]=sizei[son[x][0]]+sizei[son[x][1]]+same[x];
}
3.2 插入insert
从根节点开始向下找。
val[x] :x节点关键码大小
key :x节点优先级大小
root :根节点
cnt :当前Treap中结点的个数
vali :被插入的关键码的值
如果cnt==0 ,新建节点,返回 ,不用更新(就孤零零的一个节点更新个啥?);
如果 val[x]==vali ,只更新 sizei[x] 与 same[x] 的值,加一;
否则,继续插入,直到满足以上两个情况,如果被更新的子树的优先级大于x的优先级,向反方向旋转。
(旋转吧,小陀螺~——来自蔡明小品)
代码如下:
inline void insert(int &x,int vali){
if(!x){
x=++cnt;
sizei[x]=same[x]=1;
val[x]=vali;
key[x]=rand();
return ;
}
if(val[x]==vali)return (void)(++sizei[x],++same[x]);
int flag=vali>val[x];
insert(son[x][flag],vali);
if(key[x]<key[son[x][flag]])rotate(x,flag^1);
update(x);
}
温馨提示:记得更新。
3.3 删除deletei
vali :被删除的关键码的值
如果 val[x]==vali :
如果没有左儿子也没有右儿子,同insert正好相反,更新 sizei[x] 与 same[x] 的值,减一。如果这个节点被删空了,删除这个节点。
如果有且仅有左儿子或有且仅有右儿子,把它仅有的儿子旋转上来——也就是把它反方向旋转下去,删除旋转后的它。当然,这个节点必须到这棵子树的叶子节点才会被删除。
如果既有左儿子又有右儿子,比较左右儿子优先级的大小,优先级大的旋转上来,——也就是把它反着优先级较大儿子的方向旋转下去,删除旋转后的它。当然,这个节点也必须到这棵子树的叶子节点才会被删除。
代码如下:
inline void deletei(int &x,int vali){
if(!x)return ;
int flag=vali==val[x]?-1:(vali<val[x]?0:1);
if(flag!=-1)deletei(son[x][flag],vali);
else{
if(!son[x][0]&&!son[x][1]){
--same[x];--sizei[x];
if(same[x]==0)x=0;
}else if(son[x][0]&&!son[x][1]){
rotate(x,1);
deletei(son[x][1],vali);
}else if(!son[x][0]&&son[x][1]){
rotate(x,0);
deletei(son[x][0],vali);
}else if(son[x][0]&&son[x][1]){
flag=key[son[x][0]]>key[son[x][1]];
rotate(x,flag);
deletei(son[x][flag],vali);
}
}
update(x);
}
记得更新~
3.4 根据关键码大小找排名 (默认排名为比它小的数的个数+1)score_rand
k :被查找排名的关键码的值
如果找到空节点,返回;
如果 val[x]==k ,返回左儿子大小+1;
如果 val[x]>k ,往左儿子找;
反之往右儿子找。
代码如下:
inline int score_rank(int x,int k){
if(!x)return 0;
if(val[x]==k)return sizei[son[x][0]]+1;
else if(val[x]>k)return score_rank(son[x][0],k);
else if(val[x]<k)return score_rank(son[x][1],k)+sizei[son[x][0]]+same[x];
}
没有旋转,不用更新~
3.5 根据排名查找分数rank_score
k :被查找分数的排名的值
如果找到空节点,返回;
如果左子树大小大于等于k,查找左子树;
如果左子树大小+与x相同的值的大小大于等于k,直接返回x的关键码的值;
否则查找右子树。
代码如下:
inline int rank_score(int x,int k){
if(!x)return 0;
if(sizei[son[x][0]]>=k)return rank_score(son[x][0],k);
else if(sizei[son[x][0]]+same[x]>=k)return val[x];
else return rank_score(son[x][1],k-sizei[son[x][0]]-same[x]);
}
3.6 查找前驱pre
前驱一定是被查找的关键码的值左边最大值。
k :被查找的关键码的值
inf :自定义的正无穷的值
如果查到空节点,返回会负无穷;
如果 val[x]>=k ,往左子树找;
否则返回x节点的关键码和往左子树查找结果的关键码的最大值。
代码如下:
inline int pre(int x,int k){
if(!x)return -inf;
if(val[x]>=k)return pre(son[x][0],k);
else return max(val[x],pre(son[x][1],k));
}
3.7 查找后继suf
同查找前驱相反,只不过返回正无穷,而且是当 val[x]<=k 时往右子树找。
代码如下:
inline int suf(int x,int k){
if(!x)return inf;
if(val[x]<=k)return suf(son[x][1],k);
else return min(val[x],suf(son[x][0],k));
}
模板
https://www.luogu.com.cn/problem/P3369
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#define IOS ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int N=1e5+10;
const int inf=0x3f3f3f3f;
int n,cnt,root,son[N][2],fa[N],val[N],key[N],sizei[N],same[N];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
inline void update(int x){
sizei[x]=sizei[son[x][0]]+sizei[son[x][1]]+same[x];
}
inline void rotate(int &x,int flag){
int y=son[x][flag^1];
int change=son[y][flag];
son[x][flag^1]=change;
son[y][flag]=x;
update(x);
update(y);
x=y;
}
inline void insert(int &x,int vali){
if(!x){
x=++cnt;
sizei[x]=same[x]=1;
val[x]=vali;
key[x]=rand();
return ;
}
if(val[x]==vali)return (void)(++sizei[x],++same[x]);
int flag=vali>val[x];
insert(son[x][flag],vali);
if(key[x]<key[son[x][flag]])rotate(x,flag^1);
update(x);
}
inline void deletei(int &x,int vali){
if(!x)return ;
int flag=vali==val[x]?-1:(vali<val[x]?0:1);
if(flag!=-1)deletei(son[x][flag],vali);
else{
if(!son[x][0]&&!son[x][1]){
--same[x];--sizei[x];
if(same[x]==0)x=0;
}else if(son[x][0]&&!son[x][1]){
rotate(x,1);
deletei(son[x][1],vali);
}else if(!son[x][0]&&son[x][1]){
rotate(x,0);
deletei(son[x][0],vali);
}else if(son[x][0]&&son[x][1]){
flag=key[son[x][0]]>key[son[x][1]];
rotate(x,flag);
deletei(son[x][flag],vali);
}
}
update(x);
}
inline int score_rank(int x,int k){
if(!x)return 0;
if(val[x]==k)return sizei[son[x][0]]+1;
else if(val[x]>k)return score_rank(son[x][0],k);
else if(val[x]<k)return score_rank(son[x][1],k)+sizei[son[x][0]]+same[x];
}
inline int rank_score(int x,int k){
if(!x)return 0;
if(sizei[son[x][0]]>=k)return rank_score(son[x][0],k);
else if(sizei[son[x][0]]+same[x]>=k)return val[x];
else return rank_score(son[x][1],k-sizei[son[x][0]]-same[x]);
}
inline int pre(int x,int k){
if(!x)return -inf;
if(val[x]>=k)return pre(son[x][0],k);
else return max(val[x],pre(son[x][1],k));
}
inline int suf(int x,int k){
if(!x)return inf;
if(val[x]<=k)return suf(son[x][1],k);
else return min(val[x],suf(son[x][0],k));
}
int main(){
freopen("P3369.out","w",stdout);
//IOS;
//cin>>n;
n=read();
for(int i=1;i<=n;i++){
int op,x;
//cin>>op>>x;
op=read();x=read();
if(op==1)insert(root,x);
else if(op==2)deletei(root,x);
else if(op==3)cout<<score_rank(root,x)<<endl;
else if(op==4)cout<<rank_score(root,x)<<endl;
else if(op==5)cout<<pre(root,x)<<endl;
else if(op==6)cout<<suf(root,x)<<endl;
}
return 0;
}
2021.12.06 平衡树——Treap的更多相关文章
- 2021.12.08 平衡树——FHQ Treap
2021.12.08 平衡树--FHQ Treap http://www.yhzq-blog.cc/fhqtreapzongjie/ https://www.cnblogs.com/zwfymqz/p ...
- 2021.12.06 P2511 [HAOI2008]木棍分割(动态规划)
2021.12.06 P2511 [HAOI2008]木棍分割(动态规划) https://www.luogu.com.cn/problem/P2511 题意: 有n根木棍, 第i根木棍的长度为 \( ...
- 2021.12.06 P2508 [HAOI2008]圆上的整点(数论+ π )
2021.12.06 P2508 [HAOI2008]圆上的整点(数论+ \(\pi\) ) https://www.luogu.com.cn/problem/P2508 题意: 求一个给定的圆 \( ...
- 2021.12.06 P1450 [HAOI2008]硬币购物(组合数学+抽屉原理+DP)
2021.12.06 P1450 [HAOI2008]硬币购物(组合数学+抽屉原理+DP) https://www.luogu.com.cn/problem/P1450 题意: 共有 44 种硬币.面 ...
- 2021.12.06 P2501 [HAOI2006]数字序列(动态规划+LIS)
2021.12.06 P2501 [HAOI2006]数字序列(动态规划+LIS) https://www.luogu.com.cn/problem/P2501 题意: 现在我们有一个长度为 n 的整 ...
- 2021.12.08 [SHOI2009]会场预约(平衡树游码表)
2021.12.08 [SHOI2009]会场预约(平衡树游码表) https://www.luogu.com.cn/problem/P2161 题意: 你需要维护一个 在数轴上的线段 的集合 \(S ...
- 2021.12.07 P4291 [HAOI2008]排名系统(Treap)
2021.12.07 P4291 [HAOI2008]排名系统(Treap) https://www.luogu.com.cn/problem/P4291 双倍经验: https://www.luog ...
- 2021.12.07 [TJOI2013]最长上升子序列(Treap+DP)
2021.12.07 [TJOI2013]最长上升子序列(Treap+DP) https://www.luogu.com.cn/problem/P4309 题意: 给定一个序列,初始为空.现在我们将1 ...
- hiho #1325 : 平衡树·Treap
#1325 : 平衡树·Treap 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho:小Hi,我发现我们以前讲过的两个数据结构特别相似. 小Hi:你说的是哪两个啊? ...
随机推荐
- 4月23日 python学习总结 套接字UDP和 操作系统理论,多道理论
一.套接字UDP udp是无链接的,先启动哪一端都不会报错 UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务.不会使用块的合并优化算法,, ...
- 4月16日 python学习总结 封装之property、多态 、classmethod和staticmethod
一.封装之property @property把一个函数伪装成一个数据类型 @伪装成数据的函数名.setter 控制该数据的修改,修改该数据时触发 @伪装成数据的函数名.delect 控制该数 ...
- /proc/uptime参数的意义
有关/proc/uptime这个文件里两个参数所代表的意义: [root@app ~]#cat /proc/uptime 3387048.81 3310821.00 第一个参数是代表从系统启动到现在的 ...
- 10年.NET老程序员推荐的7个开发类工具
做.NET软件工作已经10年了,从程序员做 到高级程序员,再到技术主管,技术总监.见证了Visual Studio .NET 2003,Visul Studio 2005, Visual Studio ...
- 【编程教室】Python绘制冬奥吉祥物“冰墩墩”
大家好,欢迎来到 Crossin的编程教室 ! 这两天,随着北京冬奥会的开幕,吉祥物"冰墩墩"可是火出了圈,多少人排长队都买不到.据说甚至有人把价格炒到了几千元. 就连昨天的&qu ...
- springboot 配置文件的优先级和互补配置
一.springboot启动时候,配置文件的优先级如下所示由高到低.高优先级会覆盖低优先级相同配置,并且和低优先级形成互补配置. –file:./config/ ###根目录config目录下 –fi ...
- struts2学习一:hello struts2及struts2环境配置中遇到的问题
17年下半年的时候简单学了下strus2,好吧,现在已经全忘了,idea也是刚开始用,本来想按教程写个hello struts2,结果,出了以下系列问题. pre:step1-5是我按照百度的教程搭的 ...
- char型变量中能不能存贮一个中文汉字?为什么?
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦.不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么 ...
- jQuery--基本过滤选择器
1.基本过滤选择器介绍 基本过滤器: :first 获取数组中第一个元素 :last 获取数组中最后一个 :eq(selector) 获取指定索引 :gt(index) 大于 ...
- 哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入?
你两种依赖方式都可以使用,构造器注入和Setter方法注入.最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖.