浅谈fhq treap

写在前面

为什么要先写\(fhq\ treap\)呢?
因为它好理解,而且好写啊(破音)!

一种依靠分裂(\(spilt\))和合并(\(merge\))操作实现的平衡树,由大神范浩强发明,所以叫\(fhq\ treap\)

优点

码量小而且核心操作是复读机(就是直接复制改一点东西然后就行)

易于理解

缺点

常数略大

操作

节点信息

左右子树编号 ,值,索引(就是\(rand()\)的值),子树大小(找排名,\(pre\)\(next\)时用)

struct node
{
    int l,r;
    int val,key;
    int size;
}fhq[N];
int cnt,root;
int nownode(int val)
{
    fhq[++cnt].val=val;
    fhq[cnt].size=1;
    fhq[cnt].key=rand();
    return cnt;
}

分裂(spilt)

分裂由两种:按值分裂和按大小分裂

这里仅介绍按值分裂,按大小分裂(可以维护区间分裂)在我做完P3391 【模板】文艺平衡树的时候会补上

按值分裂:把树拆成两棵树,拆出来的一棵树的值全部小于等于给定的值,另外一部分的值全部大于给定的值

int spilt(int now,int val,int &x,int &y) {//x和y是拆分后两树的根节点
    if(!now) x=y=0;
    else {
        if(fhq[now].val<=val) {
            x=now;
            spilt(fhq[now].r,val,fhq[now].r,y);//拆分右子树,因为右子树中可能还存在比val小的节点
        } else {
            y=now;
            spilt(fhq[now].l,val,x,fhq[now].l);//复读机
        }
        update(now);
    }
}

合并(merge)

合并就是把两棵树\(x,y\)合并成一棵树,其中x树上的所有值都小于\(y\)树上的所有值。而且新合并出来的树依旧满足\(Treap\)的性质

int merge(int x,int y) {
    if(!x||!y) return x+y;
    if(fhq[x].key>fhq[y].key) { //维护根的性质
        //根据堆的性质得到的新树x在y上面,而且根据二叉搜索树的性质y一定在x下面,所有y在x右下
        fhq[x].r=merge(fhq[x].r,y);
        update(x);
        return x;
    } else { //复读机
        fhq[y].l=merge(x,fhq[y].l);
        update(y);
        return y;
    }
}

插入

插入权值val

按值\(val\)把树分裂成\(x\)\(y\)

合并\(x\),新节点,\(y\)

只需要两行代码

解释一下为什么可以这样

按值权值\(val\)分裂得到了两棵树\(x\),\(y\)

那么x树上的所有值一定小于等于\(val\),y树上所有值一定大于\(val\),所以就可以直接合并\(x\),新节点,\(y\)

void ins(int val) {
    spilt(root,val,x,y);
    root=merge(merge(x,nownode(val)),y);//这里忘给root负值了 
}

删除

只需要四行代码

先按照\(val\)把树分裂成\(x\)\(z\)

然后按照\(val-1\)把树\(x\)分裂成\(x\)\(y\)

那么此时\(y\)树上所以值都是等于\(val\)的,我们去掉它的根节点(让\(y\)等于合并\(y\)的左子树和右子树)

void del(int val) {
    spilt(root,val,x,z);
    spilt(x,val-1,x,y);
    y=merge(fhq[y].l,fhq[y].r);
    root=merge(merge(x,y),z);
}

查询值的排名

按照\(val-1\)分裂成\(x\)\(y\)

\(x\)的大小+1就是\(val\)的排名

最后把x和y合并

int getrank(int val) {
    spilt(root,val-1,x,y);
    root=merge(x,y);
    return fhq[x].size+1;
}

查询排名的值

不写了,看代码也很好理解

int getnum(int pm) {
    int now=root;
    while(now) {
        if(fhq[fhq[now].l].size+1==pm) break;
        else if(fhq[fhq[now].l].size>=pm) now=fhq[now].l;
        else {
            pm-=fhq[fhq[now].l].size+1;
            now=fhq[now].r;
        }
    }
    return fhq[now].val;
}

前驱/后继

不知道是什么的看我上一篇博客

前驱:按照\(val-1\)分裂成\(x\)\(y\),则\(x\)里面最右的数就是\(val\)的前驱

前驱:按照\(val\)分裂成\(x\)\(y\),则\(y\)里面最左的数就是\(val\)的前驱

int pre(int val) {
    spilt(root,val-1,x,y);
    int now=x;
    while(fhq[now].r) now=fhq[now].r;
    root=merge(x,y);
    return fhq[now].val;
}
int Next(int val) {
    spilt(root,val,x,y);
    int now=y;
    while(fhq[now].l) now=fhq[now].l;
    root=merge(x,y);
    return fhq[now].val;
}

未完待续...上面的代码还没ac,谨慎使用

猜你喜欢

转载自www.cnblogs.com/pyyyyyy/p/12070161.html