FHQ-treap 学习笔记

FHQ-treap 类似带旋treap 每个点维护键值对 依靠随机维护的形态
但它不需要复杂的旋转操作 主要依靠暴力分裂和合并 代码极其好写
常数较小 仅次于带旋treap 但代码难度远小于带旋treap

int siz[N],ls[N],rs[N],rd[N],val[M],cnt;

首先维护如下信息:

  • \(siz\) :子树大小
  • \(ls/rs\) :当前节点左/右儿子
  • \(val\) :节点值
  • \(rd\) :随机键值
  • \(cnt\) :节点数

pushup

inl void pushup(int k){siz[k]=siz[ls[k]]+siz[rs[k]]+1;}

很好理解 维护子树大小

split

FHQ-treap 经典操作

inl void split(int k,int x,int &l,int &r){
    if(!k)return l=r=0,void();
    if(val[k]<=x){
        l=k;
        split(rs[k],x,rs[k],r);
    }else{
        r=k;
        split(ls[k],x,l,ls[k]);
    }
    pushup(k);
}

\(split(rt,x,l,r)\) 会把 \(rt\) 为根的子树拆成 \(\le x\)\(>x\) 两部分
并返回他们的根 即为 \(l,r\)

如果 $x\ge $当前点值 那么左子树一定为 \(\le x\) 部分 把 \(l\) 设为 \(k\)
此时右子树可能还存在值 \(\le x\) 的点 所以向右子树递归 把剩余的部分接到当前点右儿子上
另一种情况同理

merge

inl int merge(int x,int y){
    if(!x||!y)return x+y;
    if(rd[x]<=rd[y]){
        rs[x]=merge(rs[x],y);
        return pushup(x),x;
    }else{
        ls[y]=merge(x,ls[y]);
        return pushup(y),y;
    }
}

\(merge(x,y)\) 会把以 \(x\)\(y\) 为根的子树合并 并返回根节点的值
如果有一个是叶子节点 那么就并完了 返回另一个即可
然后这里注意 \(merge\) 函数要求每次调用都保证 \(y\) 子树内元素值 \(\ge\) \(x\) 子树内元素值
否则左右儿子值是乱的 无法保证左子树值 \(<\) 右子树值
或者你合并前特判 \(swap\) 一下也行)
但无论如何 一定把值更大的 \(y\) 并到 \(x\) 右儿子 或者 值小的 \(x\) 并到 \(y\) 的左儿子

求x数的排名等平衡树常规操作

int x=read();
int p,q;
treap.split(rt,x-1,p,q);
cout<<treap.siz[p]+1<<endl;
rt=treap.merge(p,q);

\(<x\) 的数拆出来 排名就是子树大小+1 然后在并回去
注意FHQ每次插入都会新开节点 不同于splay和带旋treap
所以朴素求排名还有如下写法:

inl int rank(int k,int x){
    if(!k)return 1;
    if(val[k]>=x)return rank(ls[k],x);
    if(val[k]<x)return siz[ls[k]]+1+rank(rs[k],x);
}

其余操作相同

inl int pre(int k,int x){
    if(!k)return -inf;
    if(val[k]>=x)return pre(ls[k],x);
    return max(val[k],pre(rs[k],x));
}
inl int suc(int k,int x){
    if(!k)return inf;
    if(val[k]<=x)return suc(rs[k],x);
    return min(val[k],suc(ls[k],x));
}
posted @ 2023-12-01 11:30  xiang_xiang  阅读(10)  评论(0编辑  收藏  举报