underthetwilight

FHQ-Treap

\[\Large\text{FHQ-Treap} \]

\(1.\)平衡树的定义,性质

平衡树,顾名思义就是所有子树大小较为均衡的二叉搜索树,其特性在于对于每次到叶子节点的操作,其期望复杂度是\(O(\log n)\)的,这样我们就能在可接受复杂度内完成一次操作

\(2.\text{Treap}\)的结构及其特性

\(\text{Treap=Tree+Heap}\) 是一个二叉搜索树和堆的结合体
\((1).\)二叉搜索树
即一颗二叉树,满足对于任意的节点,其左子树的所有权值均小于其自身权值,其右子树权值均大于其权值(左右子树均存在)称为一颗二叉搜索树\(\text{(Binary Search Tree)}\)
性质:对\(\text{BST}\)进行中序遍历,得到的序列一定是严格递增的,反之,若一棵树的中序遍历递增,其为二叉搜索树。
\((2).\)\((\text{Heap})\)
为一颗完全二叉树,以大根堆为例其满足对于任意节点,其权值恒大于左右儿子,根节点储存全局最大值,小根堆反之

\(\text{3.FHQ-treap}\)

\((1).\)对于树上的每个节点,我们维护以下的信息
\((\text i).lc,rc\) 该节点的左右儿子编号
\((\text{ii}).val\) 该节点的权值,符合二叉搜索树结构
\((\text{iii}).key\) 一个随机值,符合堆的性质,以此来满足平衡性
\((2).\text{FHQ-treap}\)是一种非旋\(\text{treap}\)其功能基于两个函数实现
\(\text{Function1: Split:}\)
该函数的功能是将整颗\(\text{Treap}\)按某个权值\(val\)分成两个,满足左侧\(\text{Treap}\)的权值\(\le val\),右侧全部\(> val\),该函数的实现过程是这样的
\(\text{Case 1 :}\)若当前节点的值\(\le val\),即该节点应被划分到左侧,其左子树显然也应该被分到左侧,然后递归右子树
\(\text{Case 2 :}\)若当前节点的值\(> val\),即该节点应被划分到右侧,其右子树显然也应该被分到右侧,然后递归左子树

void split(int u,int v,int &x,int &y){
    if(!u){x=y=0;return;}
    if(t[u].v<=v){
        x=u;split(t[u].rc,v,t[u].rc,y);
    }else{y=u;split(t[u].lc,v,x,t[u].lc);}
    pushup(u);
}

\(\text{Function2:Merge:}\)
该函数即将两个\(\text{Treap}\)按根节点的\(key\)合并,并使其满足堆的性质,其本质是将两个区间首尾相接,所以\(\text{Merge}\)函数实际上是对于任意的两段区间都可以进行,由此可见\(\text{FHQ-treap}\)实际上维护的是一段序列
注意:在使用以下的代码时,务必保证\(x\)树内的最大值小于\(y\)树内的最小值

int merge(int x,int y){
    if(!x||!y) return x+y;
    if(t[x].key>t[y].key){//维护Heap结构
        t[x].rc=merge(t[x].rc,y);//维护BST结构
        pushup(x);return x;
    }else{
        t[y].lc=merge(x,t[y].lc);
        pushup(y);return y;
    }
}

\((3).\text{FHQ-treap}\)的其他功能
\(\text{(i). insert}\) 插入数值\(val\)
即将原树\(split\)\(\le val,>val\)两棵树,插入一个值为\(val\)的新节点,\(merge\)即可
\(\text{(ii). erase}\) 删除数值为\(val\)的元素中的一个/全部
一个:分裂为\(<val,=val,>val\)三棵子树,删除\(=val\)中的根节点,依次合并即可
全部:分裂同上,合并时直接合并\(>val,<val\)两子树
\(\text{(iii). rank}\) 返回排名为\(k\)的元素的数值
本质上是平衡树上二分,维护左右子树的\(sz\)信息,并使用\(pushup\)操作去维护,如果左子树\(sz\ge k\),递归左子树,否则\(k-=sz_{lc}\)若此时\(k=1\),返回该节点权值,否则递归右子树
\(\text{(iV). pre}\) 输出数值为\(val\)的前驱
分裂为\(<val,\ge val\) 两子树,在\(<val\)子树中不断递归右节点,直至空,返回最右侧叶子节点,后继与之类似

$\text{Code}$
mt19937 rnd(time(0));
struct node{
    int lc,rc,v,key,sz;
    void init(int val){v=val;key=rnd();sz=1;}
}t[N];int tot,rt;
int newnode(int val){
    tot++;t[tot].init(val);
    return tot;
}//建节点
void pushup(int u){t[u].sz=t[t[u].lc].sz+t[t[u].rc].sz+1;}
void split(int u,int v,int &x,int &y){
    if(!u){x=y=0;return;}
    if(t[u].v<=v){
        x=u;split(t[u].rc,v,t[u].rc,y);
    }else{y=u;split(t[u].lc,v,x,t[u].lc);}
    pushup(u);
}
int merge(int x,int y){
    if(!x||!y) return x+y;
    if(t[x].key>t[y].key){
        t[x].rc=merge(t[x].rc,y);
        pushup(x);return x;
    }else{
        t[y].lc=merge(x,t[y].lc);
        pushup(y);return y;
    }
}
void insert(int val){
    int x=0,y=0;
    split(rt,val,x,y);
    rt=merge(x,merge(newnode(val),y));
}
void erase(int val){
    int x=0,y=0,z=0;
    split(rt,val,x,y);
    split(x,val-1,x,z);
    z=merge(t[z].lc,t[z].rc);
    rt=merge(x,merge(z,y));
}
int lessthan(int val){
    int x=0,y=0;
    split(rt,val-1,x,y);
    int k=t[x].sz+1;
    rt=merge(x,y);return k;
}//有多少个元素比他小
int getrank(int k){
    int u=rt;
    while(u){
        if(t[t[u].lc].sz>=k) u=t[u].lc;
        else{
            k-=t[t[u].lc].sz;
            if(k==1) return t[u].v;
            else k--,u=t[u].rc;
        }
    }
}
int pre(int val){
    int x=0,y=0;
    split(rt,val-1,x,y);
    int z=x;
    while(t[z].rc) z=t[z].rc;
    rt=merge(x,y);
    return t[z].v;
}
int nxt(int val){
    int x=0,y=0;
    split(rt,val,x,y);
    int z=y;
    while(t[z].lc) z=t[z].lc;
    rt=merge(x,y);
    return t[z].v;
}

\(4.\)序列\(\text{FHQ-treap}\)

即将上述的权值平衡树中的所有\(val\)换为数组下标,可以利用类线段树的\(Lazy tag+pushdown\)操作进行类似区间加,区间反转之类操作
区间反转:
即打上懒标记,下传时交换该节点的左右子树,类似\(\text{01-trie}\)中的按位取反操作

posted on 2026-08-05 14:10  Underthetwilight  阅读(8)  评论(0)    收藏  举报

导航