简析平衡树(四)——FHQ Treap
前言
好久没码过平衡树了!
这次在闪指导的指导下学会了\(FHQ\ Treap\),一方面是因为听说它可以可持久化,另一方面则是因为听说它是真的好写。
简介
\(FHQ\ Treap\),又称作非旋\(Treap\)。
其实在我看来,它与\(Treap\)的共同点也只有都借助了随机键值来维护平衡。
具体实现起来,两者真是大为不同。
不过,为助于理解,还是在这里贴上\(Treap\)的博客吧:简析平衡树(二)——Treap。
\(FHQ\ Treap\)的核心操作
其他内容我也就不多说了,下面就从\(FHQ\ Treap\)的两个核心操作讲起吧。
核心操作\(1\):\(Merge\)
\(Merge\),即为合并,感觉与线段树、左偏树等数据结构的合并有些神似。
首先我们判断当前合并的两个节点中是否有空节点,有就直接返回。
然后,我们比较二者键值大小,让键值大的作为当前节点,并递归合并其子节点和键值小的节点。
代码如下:
I void Merge(int& k,RI x,RI y)//合并x和y,存储到k,其中x中的元素小于等于y中的元素
{
	if(!x||!y) return (void)(k=x+y);//如果有空节点,直接返回
	O[x].D>O[y].D?(k=x,Merge(SX)):(k=y,Merge(SY)),PU(k);//比较键值,递归合并
}
核心操作\(2\):\(Split\)操作
\(Split\),即为分裂,这是一些普通平衡树没有的操作。
与\(Merge\)类似,因为它本来就是\(Merge\)的逆操作。
这里的分裂是按照一定标准进行分裂的,这里以按权值大小分裂为例。
首先我们判断当前分裂的节点是否为空节点,是则直接返回。
然后,若当前权值小于等于分裂权值,存到第一棵树中,否则存到第二棵树中。
代码如下:
I void Split(RI k,int& x,int& y,CI v)//分裂k,存储到x和y,其中小于等于v的元素存储到x,大于v的元素存储到y
{
	if(!k) return (void)(x=y=0);//如果当前分裂节点为空,直接返回
	O[k].V<=v?(x=k,Split(SX,v)):(y=k,Split(SY,v)),PU(k);//按权值分裂,递归继续分裂
}
其他操作
其他操作主要使用的就是\(Merge\)和\(Split\)两个操作,因此下面就不多加介绍了。
完整代码(板子题)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n;
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int f,T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0,f=1;W(!D) f=c^'-'?1:-1;W(x=tn+(c&15),D);x*=f;}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void write(Ty x) {x<0&&(pc('-'),x=-x);W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
		#undef D
}F;
class FHQTreap //FHQ Treap模板
{
	private:
		#define Rd() (seed=(233333LL*seed+666667)%2147483648LL)//手写随机数
		#define SX O[k].S[1],O[x].S[1],y
		#define SY O[k].S[0],x,O[y].S[0]
		#define NewNode(v) (O[++tot].Sz=1,O[tot].V=v,O[tot].D=Rd(),tot)//建立新节点
		#define PU(x) (O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+1)//上传信息
		int rt,tot,seed;struct node {int Sz,V,D,S[2];}O[N+5];
		I void Merge(int& k,RI x,RI y)//合并x和y,存储到k,其中x中的元素小于等于y中的元素
		{
			if(!x||!y) return (void)(k=x+y);//如果有空节点,直接返回
			O[x].D>O[y].D?(k=x,Merge(SX)):(k=y,Merge(SY)),PU(k);//比较键值,递归合并
		}
		I void Split(RI k,int& x,int& y,CI v)//分裂k,存储到x和y,其中小于等于v的元素存储到x,大于v的元素存储到y
		{
			if(!k) return (void)(x=y=0);//如果当前分裂节点为空,直接返回
			O[k].V<=v?(x=k,Split(SX,v)):(y=k,Split(SY,v)),PU(k);//按权值分裂,递归继续分裂
		}
		I int Find(RI k,RI rk)//找到k子树内排名为rk的点
		{
			W((O[O[k].S[0]].Sz+1)^rk) O[O[k].S[0]].Sz>=rk? //如果在左子树中
				k=O[k].S[0]:(rk-=O[O[k].S[0]].Sz+1,k=O[k].S[1]);//否则在右子树中
			return k;//返回答案
		}
	public:
		I FHQTreap() {seed=20050521;}//初始化随机种子
		I void Insert(CI v)//插入元素
		{
			RI x=0,y=0,k=NewNode(v);//新建一个权值为当前插入值的点
			Split(rt,x,y,v),Merge(x,x,k),Merge(rt,x,y);//分裂为小于等于v和大于v的两棵树,然后依次合并
		}
		I void Delete(CI v)//删除元素
		{
			RI x=0,y=0,k=0;Split(rt,x,y,v),Split(x,x,k,v-1),//先通过两次合并,此时k子树中值全为v
			Merge(k,O[k].S[0],O[k].S[1]),Merge(x,x,k),Merge(rt,x,y);//合并k的两个子节点(即删除k的根节点),然后依次合并
		}
		I int GetRk(CI v) {RI x=0,y=0,k;return Split(rt,x,y,v-1),k=O[x].Sz+1,Merge(rt,x,y),k;}//求给定值的排名,分裂出小于v的树,其Size+1即为v的排名
		I int GetVal(CI v) {return O[Find(rt,v)].V;}//求给定排名的值,直接调用Find()函数
		I int GetPre(CI v) {RI x=0,y=0,k;return Split(rt,x,y,v-1),k=Find(x,O[x].Sz),Merge(rt,x,y),O[k].V;}//求前驱,分裂出小于v的树,其中最大的值即为v的前驱
		I int GetNxt(CI v) {RI x=0,y=0,k;return Split(rt,x,y,v),k=Find(y,1),Merge(rt,x,y),O[k].V;}//求后继,分裂出大于v的树,其中最小的值即为v的后继
}T;
int main()
{
	RI Qt,op,x;F.read(Qt);W(Qt--) switch(F.read(op,x),op)
	{
		case 1:T.Insert(x);break;//插入元素
		case 2:T.Delete(x);break;//删除元素
		case 3:F.writeln(T.GetRk(x));break;//求给定值的排名
		case 4:F.writeln(T.GetVal(x));break;//求给定排名的值
		case 5:F.writeln(T.GetPre(x));break;//求前驱
		case 6:F.writeln(T.GetNxt(x));break;//求后继
	}return F.clear(),0;
}
简析平衡树(四)——FHQ Treap的更多相关文章
- 简析平衡树(三)——浅谈Splay
		前言 原本以为\(Treap\)已经很难了,学习了\(Splay\),我才知道,没有最难,只有更难.(强烈建议先去学一学\(Treap\)再来看这篇博客) 简介 \(Splay\)是平衡树中的一种,除 ... 
- LOJ#105. 文艺平衡树(FHQ Treap)
		题面 传送门 题解 \(FHQ\ Treap\)比起\(Splay\)还是稍微好写一点--就是老是忘了要下穿标记-- //minamoto #include<bits/stdc++.h> ... 
- 洛谷P3369 【模板】普通平衡树(FHQ Treap)
		题面 传送门 题解 写了一下\(FHQ\ Treap\) //minamoto #include<bits/stdc++.h> #define R register #define inl ... 
- 简析平衡树(二)——Treap
		前言 学完了替罪羊树,我决定再去学一学\(Treap\).一直听说\(Treap\)很难,我也花了挺久才学会. 简介 \(Treap\)这个名字真的挺有内涵: \(\color{red}{Tree}\ ... 
- 简析平衡树(一)——替罪羊树 Scapegoat Tree
		前言 平衡树在我的心目中,一直都是一个很高深莫测的数据结构.不过,由于最近做的题目的题解中经常出现"平衡树"这三个字,我决定从最简单的替罪羊树开始,好好学习平衡树. 简介 替罪羊树 ... 
- 洛谷P5055 【模板】可持久化文艺平衡树(FHQ Treap)
		题面 传送门 题解 日常敲板子.jpg //minamoto #include<bits/stdc++.h> #define R register #define inline __inl ... 
- 洛谷P3835 【模板】可持久化平衡树(FHQ Treap)
		题面 传送门 题解 可持久化一下就好了,具体可以看代码 这里有一个小\(trick\)就是我们原本在\(merge\)的时候也要新建节点的,但是我们\(merge\)之前一般已经\(split\)过了 ... 
- 在平衡树的海洋中畅游(四)——FHQ Treap
		Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ... 
- 简析TCP的三次握手与四次分手【转】
		转自 简析TCP的三次握手与四次分手 | 果冻想http://www.jellythink.com/archives/705 TCP是什么? 具体的关于TCP是什么,我不打算详细的说了:当你看到这篇文 ... 
随机推荐
- Win10修改hosts文件并配置DNS
			1.打开C:\Windows\System32\drivers\etc目录 2.去掉hosts文件的只读属性 3.添加dns解析配置 127.0.0.1 www.example.c ... 
- JavaScript对象及初识面向对象
			一.对象 1.1对象是什么 对象是包含相关属性和方法的集合体 1.2什么是面向对象 面向对象仅仅是一个概念或者编程思想 通过一种叫做原型的方式来实现面向对象编程 二.创建对象 2.1自定义对象 2.1 ... 
- 网页静态化技术Freemarkerh简介
			1.1为什么要使用网页静态化技术 网页静态化解决方案在实际开发中运用比较多,例如新闻网站,门户网站中的新闻频道或者是文章类的频道. 对于电商网站的商品详细页来说,至少几百万个商品,每个商品又有大量的信 ... 
- Spring自动注入,类型注入、名称注入(两种方式)
			参考: https://blog.csdn.net/qq_41767337/article/details/89002422 https://www.iteye.com/blog/breezylee- ... 
- AngleSharp 实战(05)之遍历内部子元素(x)元素,尝试着获取元素的 Attr 和 InnerText
			直接贴代码了: using System; using System.Linq; using System.Threading.Tasks; using AngleSharp; using Angle ... 
- 修改linux内核加载顺序
			修改内核启动顺序:1.查看当前系统所有的内核# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/gr ... 
- 一个jetty部署多个项目配置之方法一
			https://my.oschina.net/wangyongqing/blog/115647 Jetty用户经常想配置他们的web应用到不同的虚拟主机. 通常情况下,一个单一的IP地址的机器有不同的 ... 
- abstract,virtual,override个人
			1.abstract 可以修饰类和方法,修饰方法时只声明不实现: 2.继承实现abstract类必须通过override实现abstract声明的方法,而virtual方法可选择override(重写 ... 
- Activex在没有电子秤api的情况下获取串口数据
			大二做B/S架构的项目使用了安衡电子秤CHS-D+R和一款扫码枪,两个设备的串口使用一样,这款电子秤是相当的坑,没有开发的api,无奈只能自己开发Activex了,在B/S架构中进行引用Activex ... 
- J2EE的13种规范
			1.JDBC(Java Databaes Connectivity):JDBC API为访问不同的数据库提供了一种统一的途径,就像ODBC一样,JDBC对开发者屏蔽了一些细节问题,同时,JDBC对数据 ... 
