[模板] FHQ-Treap 平衡树

普通平衡树(分裂式)

一些原理

  • 平衡树是一种 \(key\) 满足堆的性质,\(val\) 满足 \(BST\) 性质的一种二叉树

  • 平衡树的 \(key\) 是随机生成数据,这使得他在合并的时候满足不同于 \(BST\) 的平衡性

  • 与一般的 \(BST\) 类似,需要维护左右儿子,子树 size 以及权值

操作

  • 开点操作
int nd(int x){
	val[++cnt]=x;
	fix[cnt]=rand();
	sz[cnt]=1;
	return cnt;
}

  • 合并操作(根据 \(key\)
  • 合并后满足\(x\) 树的中序遍历始终在 \(y\) 前面,因此 \(merge\) 参数的顺序是需要考虑的
int merge(int x,int y){
	if(!x||!y)return x|y;
	if(fix[x]<fix[y]){
		son[x][1]=merge(son[x][1],y),pushup(x);
		return x;
	}
	else {
		son[y][0]=merge(x,son[y][0]),pushup(y);
		return y;
	}
}

  • \(split\) 操作(核心操作)

将一棵树分裂成 \(val\leq v\)\(val>v\) 的两部分,这使得我们完成插入等操作

pair<int,int> split(int x,int v){
	if(!x)return mp(0,0);
	if(val[x]<=v){
		pair<int,int> p=split(son[x][1],v);
		son[x][1]=p.FF;pushup(x);
		return mp(x,p.SS);
	}
	else if(val[x]>v){
		pair<int,int> p=split(son[x][0],v);
		son[x][0]=p.SS;pushup(x);
		return mp(p.FF,x);
	}
}

返回的 \(pair\) 的意义是:这棵树分裂后两部分的根


  • \(insert\) 操作

分裂再合并,并开新结点,注意合并顺序

void insert(int v){
	pair<int,int> p=split(rt,v);
	rt=merge(merge(p.FF,nd(v)),p.SS);
}

  • \(erase\) 操作
void erase(int v){
	pair<int,int> p=split(rt,v),q=split(p.FF,v-1);
	q.SS=merge(son[q.SS][0],son[q.SS][1]);//删除
	rt=merge(merge(q.FF,q.SS),p.SS); 
}

分裂两次再合并回去,注意实时维护根节点


  • \(Rank\) 操作,第 \(K\)
int Rank(int v){
	pair<int,int> p=split(rt,v-1);//小于v的分裂到一边
	int res=sz[p.FF]+1;
	rt=merge(p.FF,p.SS);
	return res;
}
int Kth(int x,int k){
	if(sz[son[x][0]]+1==k)return val[x];
	if(sz[son[x][0]]+1>k)return Kth(son[x][0],k);
	else return Kth(son[x][1],k-sz[son[x][0]]-1);
}

  • 求 前驱&后继
int pre(int v){
	pair<int,int> p=split(rt,v-1);
	int x=p.FF;
	while(son[x][1])x=son[x][1];
	rt=merge(p.FF,p.SS);
	return val[x];
}
int suc(int v){
	pair<int,int> p=split(rt,v);
	int x=p.SS;
	while(son[x][0])x=son[x][0];
	rt=merge(p.FF,p.SS);
	return val[x];
}

P3369 【模板】普通平衡树

//Treap
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <ctime>
#include <utility>
using namespace std;
#define mp make_pair
const int maxn = 2e5 + 10;
int sz[maxn],val[maxn],fix[maxn],cnt,rt,son[maxn][2];
int nd(int x){
	val[++cnt]=x;
	fix[cnt]=rand();
	sz[cnt]=1;
	return cnt;
}
void pushup(int x){
	sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;return;
}
int merge(int x,int y){
	if(!x||!y)return x|y;
	if(fix[x]<fix[y]){
		son[x][1]=merge(son[x][1],y);pushup(x);
		return x;
	}
	else {
		son[y][0]=merge(x,son[y][0]);pushup(y);
		return y;
	}
}
pair<int,int> split(int x,int v){
	if(!x)return mp(0,0);
	if(val[x]<=v){
		pair<int,int> p=split(son[x][1],v);
		son[x][1]=p.first;pushup(x);
		return mp(x,p.second);
	}
	else{
		pair<int,int> p=split(son[x][0],v);
		son[x][0]=p.second;pushup(x);
		return mp(p.first,x);
	}
}
void insert(int v){
	pair<int,int> p=split(rt,v);
	rt=merge(merge(p.first,nd(v)),p.second);
}
void erase(int v){
	pair<int,int> p=split(rt,v),q=split(p.first,v-1);
	q.second=merge(son[q.second][0],son[q.second][1]);//删除
	rt=merge(merge(q.first,q.second),p.second); 
}
int Rank(int v){
	pair<int,int> p=split(rt,v-1);
	int res=sz[p.first]+1;
	rt=merge(p.first,p.second);
	return res;
}
int Kth(int x,int k){
	if(sz[son[x][0]]+1==k)return val[x];
	if(sz[son[x][0]]+1>k)return Kth(son[x][0],k);
	else return Kth(son[x][1],k-sz[son[x][0]]-1);
}
int pre(int v){
	pair<int,int> p=split(rt,v-1);
	int x=p.first;
	while(son[x][1])x=son[x][1];
	rt=merge(p.first,p.second);
	return val[x];
}
int suc(int v){
	pair<int,int> p=split(rt,v);
	int x=p.second;
	while(son[x][0])x=son[x][0];
	rt=merge(p.first,p.second);
	return val[x];
}
int n,op,x;
int main(){
	scanf("%d",&n);
	while(n--){
		scanf("%d%d",&op,&x);
		if(op==1)insert(x);
		else if(op==2)erase(x);
		else if(op==3)printf("%d\n",Rank(x));
		else if(op==4)printf("%d\n",Kth(rt,x));
		else if(op==5)printf("%d\n",pre(x));
		else if(op==6)printf("%d\n",suc(x));
	}
	return 0;
}

后记

  • 平衡树是一种可以维护前驱后继的 \(BST\) ,所以也满足 \(BST\) 的性质

\(Updated\ on \ 2021.5.14\)

一些需要注意的细节

  • 搞清本质:Treap 有服务和实际两种操作。

  • 弄清编号和权值的关系。

  • 有更改就要 \(pushup\) 更新,包括线段树也一样

更新了一下代码,变得更清晰了吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <ctime>
#include <utility>
using namespace std;
template <typename T>
inline T read(){
	char ch=getchar();T x=0;bool fl=false;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
#define mp make_pair
const int maxn = 2e5 + 10;
int sz[maxn],val[maxn],fix[maxn],cnt,rt,son[maxn][2];
inline int nd(int x){
	val[++cnt]=x;fix[cnt]=rand();sz[cnt]=1;
	return cnt;
}
inline void pushup(int x){
	sz[x]=sz[son[x][1]]+sz[son[x][0]]+1;return ;
}
int merge(int x,int y){
	if(!x||!y)return x|y;
	if(fix[x]<fix[y]){
		son[x][1]=merge(son[x][1],y);pushup(x);
		return x;
	}
	else{
		son[y][0]=merge(x,son[y][0]);pushup(y);
		return y;
	}
}
pair<int,int> split(int x,int v){
	if(x==0)return mp(0,0);//注意边界
	if(val[x]<=v){
		pair<int,int> p=split(son[x][1],v);
		son[x][1]=p.first;pushup(x);
		return mp(x,p.second);	
	}
	else{
		pair<int,int> p=split(son[x][0],v);
		son[x][0]=p.second;pushup(x);
		return mp(p.first,x);
	}
}//此时已经裂开了,因为更改了儿子信息

//---------以上为服务操作
//---------以下为实际操作
void insert(int v){
	pair<int,int> p=split(rt,v);
	rt=merge(merge(p.first,nd(v)),p.second);
}
void erase(int v){
	pair<int,int> p=split(rt,v);pair<int,int> q=split(p.first,v-1);//裂开两次
	q.second=merge(son[q.second][0],son[q.second][1]);
	rt=merge(merge(q.first,q.second),p.second);
}
int Rank(int v){
	pair<int,int> p=split(rt,v-1);
	int res=sz[p.first]+1;
	rt=merge(p.first,p.second);//先记录再合并
	return res;
}
int Kth(int x,int k){
	if(sz[son[x][0]]==k-1)return val[x];
	if(sz[son[x][0]]<k-1)return Kth(son[x][1],k-sz[son[x][0]]-1);
	else return Kth(son[x][0],k);
}
int pre(int v){
	pair<int,int> p=split(rt,v-1);
	int x=p.first;
	while(son[x][1])x=son[x][1];
	rt=merge(p.first,p.second);
	return val[x];
}
int suc(int v){
	pair<int,int> p=split(rt,v);
	int x=p.second;
	while(son[x][0])x=son[x][0];
	rt=merge(p.first,p.second);
	return val[x];
}
int n,op,x;
int main(){
	scanf("%d",&n);
	while(n--){
		scanf("%d%d",&op,&x);
		if(op==1)insert(x);
		if(op==2)erase(x);
		if(op==3)printf("%d\n",Rank(x));
		if(op==4)printf("%d\n",Kth(rt,x));
		if(op==5)printf("%d\n",pre(x));
		if(op==6)printf("%d\n",suc(x));
	}
	return 0;
}
posted @ 2021-08-12 17:06  ¶凉笙  阅读(21)  评论(0编辑  收藏  举报