什么是FHQ-Treap

\(Treap\),一种数据结构,支持插入节点、删除节点、求第\(k\)大的节点、求权值为\(k\)的节点的排名、求权值比\(k\)小的最大节点、求权值比\(k\)大的最小节点

\(Treap=Tree+heap\)

其核心思想在于在权值上维护一棵二叉查找树,在优先级上维护一个堆

有旋\(Treap\)利用旋转操作来维护堆的性质,

而无旋\(Treap\)利用有序构树维护堆的性质

如果不了解什么是\(Treap\),建议先学习一下这篇博客和这篇博客

\(FHQ-Treap\)是一种没有旋转操作的\(Treap\),由范浩强发明

它具有以下几种优势

1、码量小,思路简单,关键代码只有\(50\)行,不到\(Spaly\) 一半,打熟之后\(10\)分钟可以写完

2、速度比 \(Spaly\) 要快,在普通平衡树(数据加强版)中,用 \(Splay\) 跑大概要 \(15s\),而用\(FHQ-Treap\)只有 \(7s\)

3、扩展多,能解决序列上的问题

核心操作

首先看一下我们要记录什么

struct asd{
	int siz,son[2],val,rd;
}tr[maxn];

\(siz\) 子树大小

\(son[0/1]\):左/右儿子

\(val\):节点存储的权值

\(rd\):当前节点的随机值

我们刚刚说到\(Treap\)在优先级上维护一个堆,那么维护的依据就是这个随机值

在随机的情况下,形成的堆的深度大约是 \(log(n)\)

这也是复杂度的保证

inline void push_up(int da){
	tr[da].siz=tr[tr[da].son[0]].siz+tr[tr[da].son[1]].siz+1;
}

维护信息还是通过 \(push\_up\) 函数
注意最后加就可以
因为对于权值相同的点我们选择反复插入,而不是将它放在一个节点上

1、Split


这个操作主要是将一棵大平衡树分裂成两棵较小的平衡树
其中左半部分的值小于等于\(val\),右半部分的值大于\(val\)
所以,和\(Splay\) 不同的是,我们要定义多个根节点

inline void Split(int now,int val,int &x,int &y){//不要忘了传地址
	if(now==0){
		x=y=0;
		return;
	}//如果当前访问节点为空,将左右区间树的虚拟节点赋值为0
	if(val>=tr[now].val){//当前节点权值小于等于val,则该节点与其左子树一并归入左区间树,在左区间树中对右儿子建立虚拟节点并继续分裂右子树
		x=now;
		Split(tr[now].son[1],val,tr[now].son[1],y);
	} else {//否则将该节点与其右子树一并归入右区间树,在右区间树中对左儿子建立虚拟节点并继续分裂左子树
		y=now;
		Split(tr[now].son[0],val,x,tr[now].son[0]);
	}
	push_up(now);//更新信息
}

2、合并操作


类似于左偏树和线段树的合并操作,将两棵树合并到一起

inline int bing(int x,int y){
	if(!x || !y) return x+y;
	if(tr[x].rd<tr[y].rd){//谁的随机值小让谁当父亲节点
		tr[x].son[1]=bing(tr[x].son[1],y);
		push_up(x);
		return x;
	} else {
		tr[y].son[0]=bing(x,tr[y].son[0]);
		push_up(y);
		return y;
	}
}

注意:每次操作结束后,都要把所有的树合并到一起

其它基本操作

1、加入新节点

inline int ad(int now){
	tr[++cnt].val=now;
	tr[cnt].siz=1;
	tr[cnt].rd=rand();
	return cnt;
}

2、求第k小的数

在左右子树间来回递归即可

inline int kth(int now,int k){
	while(1){
		if(k<=tr[tr[now].son[0]].siz) now=tr[now].son[0];
		else if(k==tr[tr[now].son[0]].siz+1) return now;
		else {
			k-=tr[tr[now].son[0]].siz+1;
			now=tr[now].son[1];
		}
	}
}

3、删除一个数

如果我们将 \(x\) 这个数删除
那么我们先将整棵平衡树分成权值大于 \(x\) 的部分,和权值小于等于 \(x\) 的部分
再把权值小于等于 \(x\) 的部分分成权值小于 \(x\) 的部分和权值等于 \(x\) 的部分
最后把权值等于 \(x\) 的部分的根节点删除

Split(rt,bb,rt2,rt3);
Split(rt2,bb-1,rt1,rt2);
rt2=bing(tr[rt2].son[0],tr[rt2].son[1]);
rt=bing(bing(rt1,rt2),rt3);

4、查询一个数的排名

将整棵树分成小于\(x\) 的部分和大于等于 \(x\)的部分
直接输出小于 \(x\) 的部分的 \(siz\) 即可

Split(rt,bb-1,rt1,rt2);
cc=tr[rt1].siz+1;
rt=bing(rt1,rt2);

5、查询前驱或后继

如果查询前驱,那么把整棵树分成小于\(x\) 的部分和大于等于 \(x\)的部分
在小于 \(x\) 的部分里查询排名为树的大小的数
后继也是如此

例题(P6136 【模板】普通平衡树(数据加强版))

直接放代码

#include<cstdio>
#include<cstdlib>
#include<ctime>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=2e6+5;
struct asd{
	int siz,son[2],val,rd;
}tr[maxn];
int cnt,rt,rt1,rt2,rt3;
inline void push_up(int da){
	tr[da].siz=tr[tr[da].son[0]].siz+tr[tr[da].son[1]].siz+1;
}
inline int ad(int now){
	tr[++cnt].val=now;
	tr[cnt].siz=1;
	tr[cnt].rd=rand();
	return cnt;
}
inline int bing(int x,int y){
	if(!x || !y) return x+y;
	if(tr[x].rd<tr[y].rd){
		tr[x].son[1]=bing(tr[x].son[1],y);
		push_up(x);
		return x;
	} else {
		tr[y].son[0]=bing(x,tr[y].son[0]);
		push_up(y);
		return y;
	}
}
inline void Split(int now,int val,int &x,int &y){
	if(now==0){
		x=y=0;
		return;
	}
	if(val>=tr[now].val){
		x=now;
		Split(tr[now].son[1],val,tr[now].son[1],y);
	} else {
		y=now;
		Split(tr[now].son[0],val,x,tr[now].son[0]);
	}
	push_up(now);
}
inline int kth(int now,int k){
	while(1){
		if(k<=tr[tr[now].son[0]].siz) now=tr[now].son[0];
		else if(k==tr[tr[now].son[0]].siz+1) return now;
		else {
			k-=tr[tr[now].son[0]].siz+1;
			now=tr[now].son[1];
		}
	}
}
int lat=0,n,m,ans=0;
int main(){
	srand(time(0));
	n=read(),m=read();
	rg int aa,bb,cc;
	for(rg int i=1;i<=n;i++){
		aa=read();
		Split(rt,aa,rt1,rt2);
		rt=bing(bing(rt1,ad(aa)),rt2);
	}
	for(rg int i=1;i<=m;i++){
		aa=read(),bb=read();
		bb^=lat;
		if(aa==1){
			Split(rt,bb,rt1,rt2);
			rt=bing(bing(rt1,ad(bb)),rt2);
		} else if(aa==2){
			Split(rt,bb,rt2,rt3);
			Split(rt2,bb-1,rt1,rt2);
			rt2=bing(tr[rt2].son[0],tr[rt2].son[1]);
			rt=bing(bing(rt1,rt2),rt3);
		} else if(aa==3){
			Split(rt,bb-1,rt1,rt2);
			cc=tr[rt1].siz+1;
			rt=bing(rt1,rt2);
			ans^=cc;
			lat=cc;
		} else if(aa==4){
			cc=tr[kth(rt,bb)].val;
			ans^=cc;
			lat=cc;
		} else if(aa==5){
			Split(rt,bb-1,rt1,rt2);
			cc=tr[kth(rt1,tr[rt1].siz)].val;
			rt=bing(rt1,rt2);
			ans^=cc;
			lat=cc;
		} else {
			Split(rt,bb,rt1,rt2);
			cc=tr[kth(rt2,1)].val;
			rt=bing(rt1,rt2);
			ans^=cc;
			lat=cc;
		}
	}
	printf("%d\n",ans);
	return 0;
}

版权声明:本文为liuchanglc原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/liuchanglc/p/13802866.html