FHQ 平衡树 (一)
FHQ 平衡树
fhq 平衡树由范浩强巨佬发明,基于treap,实现无旋保持平衡
treap=tree+heap(中文翻译:树堆
-
其同时满足BST性质和堆性质
BST简单来说,就是一颗二叉树,根节点的左儿子节点所有值<根节点<根节点的右儿子的所有值堆的性质,即为二叉树中,父节点的值大于等于(或小于等于)左右儿子节点
(在treap实现这个性质中,给定一个随机值用以维护堆性质)
以这道题为例,讲解
操作
建点
struct node{
int val;
int size;//子树大小 (包括自己
int dat;//给的随机值用来维护堆
int l,r;//左右儿子
}t[maxn];
int nw(int val){
t[++tot].val=val;
t[tot].cnt=t[tot].size=1;
t[tot].dat=rand();
return tot;//返回节点编号
}
更新子树大小
用于各种操作后的节点信息更新
void push_up(int p){
t[p].size=t[t[p].l].size+t[t[p].r].size+1;//+1算上自身
}
两个核心操作
split及merge
split(分裂)
(我裂开来)
分裂分两类,按值分裂和按大小分裂
-
按值分裂
把树拆成两棵树,一棵都小于等于给定值,另一棵统统大于给定值 -
按大小分裂
把树拆成两棵树,一棵等于给定大小,剩余的放在另一棵树中
这道题是按照按值分离
维护区间信息,就按大小分裂,文艺平衡树(后面讲
为了方便起见,我们称分裂开的两个子树,一棵为左树,一棵为右树(左树的值全小于等于val,右树的值全大于val)
void split(int now,int val,int &x,int &y){//now为当前节点,val为给定值,&x为引用左树的某个节点,&y为引用右树的某个节点
if(!now) x=y=0;//这个节点不存在,那他的儿子也都不存在
else{
if(t[now].val<=val){ //给定值大于等于当前节点
x=now;//则当前节点分到左树 ,准确来说是把这个节点的左子树分给左树,因为此时左子树中的所有值小于等于val
split(t[now].r,val,t[now].r,y);//考虑右子树中有多少能分裂给左树
//根据BST性质,右子树的左子树仍有可能有小于于给定值
}
else{//反之
y=now;//则当前节点分到右树 准确来说是把这个节点的右子树分给右树,因为此时右子树中的所有值大于val
split(t[now].l,val,x,t[now].l)
//左子树的右子树,有大于给定值的可能
}
} push_up(now);//更新节点值
}
(分裂并不影响treap的性质)
merge(合并)
合并把分开的两个树合并,左树上的所有值小于等于右树上的所有值(当然合并后的树仍然满足treap性质)
int merge(int x,int y){//返回合并后树的根的编号 //要严格保证左树中的所有值<右树中的所有值即x<y
if(!x || !y) return x+y; //如果没有某个儿子就返回另一个儿子
if(t[x].dat>t[y].dat){//我们按照大根堆处理(小根堆也可
//根据大根堆,x一定是y的父亲
//根据bst性质,x的所有值都是小于y的所有值的
//所以y是x的右儿子,将x的右儿子和y合并
t[x].r=merge(t[x].r,y);
update(x);return x;//更新节点 ,返回根
}
else{//与上面相反,可推出
t[y].l=merge(x,t[y].l);
update(y);return y;
}
}
插入
插入某个值,只需要按照这个值将其分裂,再把这个左树,值
右数合并即可
左树分裂出的所有值一定小于等于val,右树一定大于等于val,y树上的所有值一定都大于等于val,val这个节点就可以当作树处理就可以与左树合并再与右树合并
void insert(int val){
int x,y;
split(root,val,x,y);//先我裂开
root=merge(merge(x,nw(val)),y);//我又合好了
}
删除
受到插入的启发(其实就是插入的逆操作),我们先按val把树分离成左树,右树
再把左树按照val-1分离成左树左,左树右
此时左树左的值全都小于val,左树右的的值都等于val
然后把左树右的根踢出去(根:我爬我爬
然后再把剩下的都合并就可以
void del(int val){
split(root,val,x,z);
split(x,val-1,x,y);
y=merge(t[y].l,t[y].r);//踢出根(不要爹),就把他的左右儿子合并
root=merge(merge(x,y),z);
}
查询值的排名
排名=小于val的值的个数+1
只需要按照val-1,分裂,左树的值都小于val,那么左树的大小+1,即为排名
void getrank(int val){
split(root,val-1,x,y);
printf("%d\n",t[x].size+1);
root=merge(x,y);//裂了还要和好
}
查询排名的值
void getval(int rank){
int now=root;
while(now){
if(t[t[now].l].size+1==rank) break;//找到排名,跳出去
else if(t[t[now].l].size>=rank) now=t[now].l;//左子树的大小等于rank,则说明val一定在左子树内(BST性质)
else{//在右子树内
rank-=t[t[now].l].size+1;//那么在右子树的排名=rank-左子树的大小+1(+1是因为算上了根节点)
now=t[now].r;
}
} printf("%d\n",t[now].val);
}
前驱
前驱:按val-1分裂成左树和右树
左树最右的数即为val的前驱
void getpre(int val){
split(root,val-1,x,y);//x为左树,y为右数
int now=x;
while(t[now].r) now=t[now].r;
printf("%d",t[now].val);
root=merge(x,y);//裂开记得拼回去
}
后继
后继:按val分裂成左树和右树
右树最左的数即为val的后继
void getnex(int val){
split(root,val,x,y);
int now=y;
while(t[now].l) now=t[now].l;
printf("%d",t[now].val);
root=merge(x,y)//同理
}
完整代码
#include<iostream>
#include<ctime>
#include<cstdio>
#include<cstdlib>
using namespace std;
int n,root,tot=0;
const int maxn=1e5+10;
int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}return x*f;
}
struct node{
int dat,size,val,l,r;
}t[maxn];
int nw(int val){
t[++tot].val=val;
t[tot].size=1;
t[tot].dat=rand();
return tot;
}
void push_up(int p){
t[p].size=1+t[t[p].l].size+t[t[p].r].size;
}
void split(int now,int val,int &x,int &y){
if(!now){
x=y=0;return ;
}
else{
if(t[now].val<=val){
x=now;
split(t[now].r,val,t[now].r,y);
}
else {
y=now;
split(t[now].l,val,x,t[now].l);
} push_up(now);
}
}
int merge(int x,int y){
if(!x||!y) return x+y;
if(t[x].dat>t[y].dat){
t[x].r=merge(t[x].r,y);
push_up(x);return x;
}else{
t[y].l=merge(x,t[y].l);
push_up(y);return y;
}
}
int x,y,z;
void inser(int val){
split(root,val,x,y);
root=merge(merge(x,nw(val)),y);
}
void del(int val){
split(root,val,x,z);
split(x,val-1,x,y);
y=merge(t[y].l,t[y].r);
root=merge(merge(x,y),z);
}
void getrank(int val){
split(root,val-1,x,y);
printf("%d\n",t[x].size+1);
root=merge(x,y);
}
void getval(int rank){
int now=root;
while(now){
if(t[t[now].l].size+1==rank) break;
if(t[t[now].l].size>=rank) now=t[now].l;
else{
rank-=t[t[now].l].size+1;
now=t[now].r;
}
}printf("%d\n",t[now].val);
}
void getpre(int val){
split(root,val-1,x,y);
int now=x;
while(t[now].r) now=t[now].r;
printf("%d\n",t[now].val);
root=merge(x,y);
}
void getnex(int val){
split(root,val,x,y);
int now=y;
while(t[now].l) now=t[now].l;
printf("%d\n",t[now].val);
root=merge(x,y);
}
int main(){
n=read();
while(n--){
int op=read(),val=read();
switch(op){
case 1:inser(val);break;
case 2:del(val);break;
case 3:getrank(val);break;
case 4:getval(val);break;
case 5:getpre(val);break;
case 6:getnex(val);break;
}
}return 0;
}