[模板] 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];
}
//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;
}