fhq treap(无旋treap) 学习笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Fantasy_World/article/details/82631238

首先最好要会写treap(也先了解一下笛卡尔树是什么。。。)
fhq treap和treap同样有一个随机分配的rnd值,用于平衡,但fhq treap不需要旋转操作来维持平衡,因为有两个神奇的操作merge和split

在两种操作之前,要明确的一点是fhq treap依靠rnd值来维护平衡,把每个点按照小根堆的方式放置,即根rnd小于子rnd,并且在任何时候都要保证有二叉搜索树的性质,即左子树key <= 根节点key < 右子树key


下面两个函数是fhq treap的核心,理解好了其他操作就十分简单

Merge:

递归的过程,在执行之前需要保证x中所有点的key值(要存储的值,不是随机值)都小于y的(简称为x < y),这是二叉搜索树的性质,而如何保证这个性质先不用着急,在插入操作中,这个性质自然实现了。最后要返回当前根节点

int merge(int x, int y) { //将xy合并为一棵新树 返回当前树根
    if(!x || !y) return x + y; // 递归边界
    if(tr[x].rnd < tr[y].rnd) {
        tr[x].rs = merge(tr[x].rs, y);//y接在x的右子树上
        update(x);
        return x;
    } else {
        tr[y].ls = merge(x, tr[y].ls);//x接在y的左子树上
        update(y);
        return y;
    }
}

Split:

权值小于等于k的分到左树(x),大于的分到右树(y)。
注意参数中的x,y是“x树中等待和别的点相接的点,y树种等待和别的点相接的点”这个意思
等待这个词很微妙,下面通过一个具体的情形解释一下
首先x,y均为0,然后判断一下,确定目前x或y的树根
若目前now的值小于等于k,则now应该放在x树上,放在x树的哪里呢?就放在函数目前的参数上,即x“等待相接的位置”,并且x左子树均比k小,不需要改动。
然后现在整棵树变为两部分 now及其左子树,now的右子树,显然在上述情形下,下一次要划分的树就是now的右子树,并且按照二叉搜索树的性质应当把划分下来的,值仍然小于等于k的子树接在x的右子树上,所以此时now的右子树就是“x等待相接的位置”
另一种情况同理
并且不用担心这样分会错(比如在第一种情况中now的右节点还是原来的,但是在不断递归的过程中now的右节点要么被更新,要么由if(!now) x = y = 0;变为0)

void split(int now, int k, int &x, int &y) {  //以权值k分离now树成为x,y树
    if(!now) x = y = 0;
    else {
        if(tree[now].val <= k) { //小于等于的划到左树
            x = now;
            split(tree[now].rson, k, tree[now].rson, y);
        } else {
            y = now;
            split(tree[now].lson, k, tree[now].lson, x);
        }
    }
    update(now);
}

解决区间问题的时候要按size分
这时k为size 若now树的左子树有>=k个节点,那么就从其左子树中找k个
否则,从其右子树中找k-tree[lson].size-1个(左边贡献了足够多的点) 减去的1是根节点(有点像treap求第k大)

void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
    if(!now) x = y = 0;
    else {
        if(tr[now].lz) down(now);
        if(k <= tr[tr[now].ls].siz) //画画图理解一下,k在now的左子树里就执行下面的语句
            y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
        else 
            x = now, split(tr[now].rs, k-tr[tr[now].ls].siz-1, tr[now].rs, y);
        update(now);
    }
}

数组模拟开点

void new_node(int val) {
    tree[++tot].size = 1, tree[tot].val = val, tree[tot].rnd = rand();
    return tot;
}

插入

为了保证”x < y”这个性质,只好按val拆分,然后在合并x和点new的时候虽然x中有和new值相同的,但是就一个点就可以先不管了,然后再合并新树和y,此时就保证了”新树 < y”

split(root, val, x, y);
root = merge(merge(x, new_node(val)), y);

删除

注意合并的时候要反着拆分的顺序操作,仍然是为了保证”x < y”
下面的操作成功分出了一个c,而c的根的值一定为val,这是因为前面划分除出了一个a,a只有两种点,值为val的点和值<=val的点,而c又是从a中分出来的

split(root, val, a, b);
split(a, val-1, a, c);
c = merge(tree[c].lson, tree[c].rson);
root = merge(merge(a,c), b);//a,c最后拆,最先合

找值为val的rank

把整棵树以 val-1 split 分为x和y 然后答案就是x.size+1,当然前提是这个值存在

找rank为k的值

和treap一样

int kth(int now,int k) {
    while(1) {
        if(k <= tr[tr[now].ls].siz) {
            now = tr[now].ls;
        } else {
            if(k == tr[tr[now].ls].siz + 1) { 
                return now;
            } else {
                k -= tr[tr[now].ls].siz + 1;
                now = tr[now].r;
            }
        }
    }
}

找val的前驱

按val-1划分,左树里面找最大的,即在左树里面找rank为左树size的即可

找val的后继

按val划分,右树rank为1的就是

区间翻转

构造一棵treap,使得对treap中序遍历就能得到初始区间

int build(int l, int r) {
    if(l > r) return 0;
    int mid = l+r>>1;
    int pos = new_node(mid);
    tr[pos].ls = build(l, mid-1);
    tr[pos].rs = build(mid+1, r);
    update(pos);
    return pos;
}

划分两次,把树分出来一个l~r的,然后进行翻转操作,具体来说就是把一个点的左右儿子交换,对于翻转区间内每一个点都进行这么个操作,最后这个区间就被整体翻转了。这里有懒标记的思想,打上标记把树合并就行了

void reverse(int l, int r) {
    int a,b,c,d;
    split(root, r, a, b);
    split(a, l-1, c, d);
    tr[d].lz ^= 1;
    root = merge(merge(c, d), b);
}

输出:中序遍历,但是不能输出虚点保存的值(虚点的位置是1和n+2,保存的值是0和n+1)
这里有懒标记的思想,所以输出时还要下传

void dfs(int now) {
    if(tr[now].lz) down(now);
    if(tr[now].ls) dfs(tr[now].ls);
    printf("%d ", tr[now].val);
    if(tr[now].rs) dfs(tr[now].rs);
}

有人说要设立两个哨兵节点处理边界。。。但是我没设立也过了,如果你知道为什么的话请在评论区指导一下我。。。

文艺平衡树:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

const int MAXN = 100000 + 10;

struct trnode{
    int val,lz,rnd,ls,rs,siz;
}tr[MAXN];

int n,m,tot,root;

void update(int now) {
    tr[now].siz = tr[tr[now].ls].siz + tr[tr[now].rs].siz + 1;
}

void down(int now) {
    tr[now].lz ^= 1;
    swap(tr[now].ls, tr[now].rs);
    tr[tr[now].ls].lz ^= 1;
    tr[tr[now].rs].lz ^= 1;
}

int new_node(int val) {
    tr[++tot].val = val;
    tr[tot].siz = 1;
    tr[tot].rnd = rand();
    tr[tot].lz = 0; //虽然原来就是0,但是仍然建议写上,当有多次询问时,我们就可以不用memset,而是直接把tot置为0,但是这样就要初始化lz
    return tot;
}

int build(int l, int r) {
    if(l > r) return 0;
    int mid = l+r>>1;
    int pos = new_node(mid);
    tr[pos].ls = build(l, mid-1);
    tr[pos].rs = build(mid+1, r);
    update(pos);
    return pos;
}

int merge(int x, int y) { //将xy合并
    if(!x || !y) return x + y; // 递归边界
    if(tr[x].lz) down(x);//下推之前 要先检验是否有,因为用的是异或清理标记,不能随便下推了
    if(tr[y].lz) down(y);
    if(tr[x].rnd < tr[y].rnd) { 
        tr[x].rs = merge(tr[x].rs, y);//y接在x的右子树上
        update(x);
        return x;
    } else {
        tr[y].ls = merge(x, tr[y].ls);//x接在y的左子树上
        update(y);
        return y;
    }
}

void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
    if(!now) x = y = 0;
    else {
        if(tr[now].lz) down(now);
        if(k <= tr[tr[now].ls].siz) //画画图理解一下,k在now的左子树里就执行下面的语句
            y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
        else 
            x = now, split(tr[now].rs, k-tr[tr[now].ls].siz-1, tr[now].rs, y);
        update(now);
    }

}

void rever(int l, int r) {
    int a,b,c,d;
    split(root, r, a, b);
    split(a, l-1, c, d);
    tr[d].lz ^= 1;
    root = merge(merge(c, d), b);
}

void dfs(int now) {
    if(tr[now].lz) down(now);
    if(tr[now].ls) dfs(tr[now].ls);
    printf("%d ", tr[now].val);
    if(tr[now].rs) dfs(tr[now].rs);
}

int main() {
    scanf("%d %d", &n, &m);
    root = build(1, n);
    for(int i=1; i<=m; i++) { 
        int l,r;
        scanf("%d %d", &l, &r);
        rever(l, r);
    }
    dfs(root);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Fantasy_World/article/details/82631238
今日推荐