FHQ-treap小结
\(FHQ\)_\(treap\)
Q:为什么学它?
A:
- 码量小、易理解、容易调、效率还不错
- 除\(LCT\)外基本都适用(\(LCT\)基本也只有\(Splay\)才能达到那个复杂度)
平衡方案:类似treap,每个节点rand一个关键字key,保证整棵树既是一个权值v的BST,又是key的heap。
基于两个基本函数:\(Split\)(分裂)和\(Merge\)(合并)
函数 | 类型 | 作用 |
---|---|---|
new_node(x) | int | 开一个权值为x的点 |
Merge(a,b) | int | 把以a为根和以b为根的树合并,返回合并后的树的根 |
Split(rt,&a,&b,x) | void | 把以root为根的树按<=x和>x分成以a和b为根的两个树 |
k_th(now,x) | int | 在now为根的树里找第x大的数 |
先讲讲数组意义
- v[]:该点权值
- w[]:该点关键字
- si[]:该点为根的子树大小
- t[][]:左(0)右(1)儿子
函数具体实现(请结合上表作用和代码注释理解)
- Split
两个A(以a为根)、B(以b为根)在分一个老树(以now为根)
在还没分到之前,a、b都只是两个位置(已经分到的节点的某一个儿子),等待着有一个符合要求的子树连在这个位置
void split(int now,int &a,int &b,int k){
if(now==0) {a=0;b=0;return;}
//如果这个点不存在
if(v[now]<=k) a=now,split(t[now][1],t[now][1],b,k);
//如果这个点权值<=k,根据BST性质,左子树的一定<=k
//那么把now及其左子树交给A(把now连在a上),然后分now的右子树
else b=now,split(t[now][0],a,t[now][0],k);
//如果这个点权值>k,根据BST性质,左子树的一定>k,同理即可
si[now]=si[t[now][0]]+si[t[now][1]]+1;
//更新这个点的si
}
- Merge
把以a为根和以b为根的树合成一个树,并返回根
要保证a的所有数都小于b
int merge(int a,int b){
if(!a||!b) return a+b;
//如果只有一个子树了,返回
if(w[a]<=w[b]){//要满足heap,所以a做根
t[a][1]=merge(t[a][1],b);
//a的左子树不变
//合并a的右子树和b
si[a]=si[t[a][0]]+si[t[a][1]]+1;
//分了只有要更新si
return a;
}
else{
t[b][0]=merge(a,t[b][0]);
si[b]=si[t[b][0]]+si[t[b][1]]+1;
return b;
}
}
- k_th
经典BST...就算不知道看一下也应该明白了
int k_th(int now,int k){
if(si[t[now][0]]>=k) return k_th(t[now][0],k);
if(si[t[now][0]]+1==k) return v[now];
return k_th(t[now][1],k-si[t[now][0]]-1);
}
操作实现
-
插入x
直接把原树按x分成a和b,合并a与x,再把a与x合并后的树与b合并(因为merge必须保证一个树的所有数都<=另一个树)
-
删除x
把原树按x-1分成a和b(>x-1),x一定在b为根的数中。然后按x把b分成b(<=x)和c,则b中的节点的权全是x。把b删掉就等于把b的左右儿子合并起来,然后再合回去就好了
-
查x排名
按x-1分成a(<=x-1)和b,si[a]+1就是x的排名
-
查第x大的数
k_th一下就好了
-
查前驱
按x-1分成a(<=x-1)和b,a中最大的数就是x的前驱,k_th(si[a])就好了
-
查后继
按x分成a和b(>x),在b中找最小的数就好了,k_th(1)就好了
注意:每次Split之后一定要Merge回去,并更新整棵树的root
#include<bits/stdc++.h>
#define ll long long
#define ri register int
using namespace std;
const int maxn=1e5+10;
int n,rt,si[maxn],v[maxn],w[maxn],t[maxn][2],cnt=0;
void split(int now,int &a,int &b,int k){
if(now==0) {a=0;b=0;return;}
if(v[now]<=k) a=now,split(t[now][1],t[now][1],b,k);
else b=now,split(t[now][0],a,t[now][0],k);
si[now]=si[t[now][0]]+si[t[now][1]]+1;
}
int merge(int a,int b){
if(!a||!b) return a+b;
if(w[a]<=w[b]){
t[a][1]=merge(t[a][1],b);
si[a]=si[t[a][0]]+si[t[a][1]]+1;
return a;
}
else{
t[b][0]=merge(a,t[b][0]);
si[b]=si[t[b][0]]+si[t[b][1]]+1;
return b;
}
}
int k_th(int now,int k){
if(si[t[now][0]]>=k) return k_th(t[now][0],k);
if(si[t[now][0]]+1==k) return v[now];
return k_th(t[now][1],k-si[t[now][0]]-1);
}
inline int New_Node(int x){
si[++cnt]=1;
v[cnt]=x;
w[cnt]=rand();
return cnt;
}
int main(){
n=rd();
rt=0;
int a,b,c,op,x;
for(ri i=1;i<=n;i++){
op=rd(); x=rd();
switch(op){
case 1:split(rt,a,b,x);rt=merge(merge(a,New_Node(x)),b);break;
case 2:split(rt,a,b,x-1);split(b,b,c,x);rt=merge(a,merge(merge(t[b][0],t[b][1]),c));break;
case 3:split(rt,a,b,x-1);printf("%d\n",si[a]+1);rt=merge(a,b);break;
case 4:printf("%d\n",k_th(rt,x));break;
case 5:split(rt,a,b,x-1);printf("%d\n",k_th(a,si[a]));rt=merge(a,b);break;
case 6:split(rt,a,b,x);printf("%d\n",k_th(b,1));rt=merge(a,b);break;
}
}
return 0;
}
无快读五六十行\
后记
功能强大的FHQ_treap还能完成treap不能完成的区间翻转,是个很优秀的算法。
如有不对,欢迎指正
结束撒花