FHQ-Treap学习笔记
什么是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;
}