[学习笔记]fhq Treap

1、引入

前置知识:堆(不需要会Treap,或者说根本不用会Treap)
我发现目前用Splay的题目都可以用fhq(不排除我见识短浅的可能)

主要两个特点:

  • 整棵树满足二叉搜索树性质:左儿子小于自己,右儿子大于自己
  • 整棵树满足堆的性质:每个点附带一个值 k e y key key,值为随机,以key满足堆(可以默认大根堆)

2、操作

新开一个节点
int addnode(int x){
    ++sz;
    val[sz] = x/*值为x*/, key[sz] = rand() * rand()/*key数组随机一个值*/, size[sz] = 1;
    return sz;
}
pushup
void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + 1; }
kth
int kth(int now, int k){//跟Splay一样的kth操作 
    while (1){
        if (size[son[now][0]] >= k) now = son[now][0]; else
        if (size[son[now][0]] + 1 >= k) return now; else
        k -= size[son[now][0]] + 1, now = son[now][1];
    }
}

前面三个不重要,真的,相信我
接下来是重头戏,fhq最最最核心的部分

split
  1. 按照值分裂
void split(int now, int w, int &u, int &v){//按照值w把1棵树掰成2棵 ,左树值均<=w,右树值均>w 
//u代表左树的当前节点,v代表右树的当前节点 
    if (!now) u = v = 0;/*当前为空节点,u、v均为0*/ else{
    	pushdown(now);//该pushdown的时候不要忘了pushdown
        if (val[now] <= w ) u = now, split(son[now][1], w, son[u][1], v); 
		//当前值<=w,属于左树,那么该点包括左子树都归到左树
		//那么将u变为now,u已经有了左儿子,目标找到右儿子 ,所以往下son[u][1]
		//now自己和左儿子都有了归属,目标把右儿子弄出去,所以往下son[now][1]
		//v什么都没干,往下v 
		else
        v = now, split(son[now][0], w, u, son[v][0]);
        //当前值>w,属于右树,那么该点包右子树都归到右树
		//那么将v变为now,v已经有了右儿子,目标找到左儿子 ,所以往下son[v][0]
		//now自己和右儿子都有了归属,目标把左儿子弄出去,所以往下son[now][0]
		//u什么都没干,往下u 
        pushup(now);//不要忘了pushup
    }
}
  1. 按照size分裂
void split(int now, int w, int &u, int &v){//按照size把1棵树掰成2棵,左树节点数为size
//u代表左树的当前节点,v代表右树的当前节点 
    if (!now) u = v = 0;/*当前为空节点,u、v均为0*/ else{
        pushdown(now);
        if (size[son[now][0]] >= w) v = now, split(son[now][0], w, u, son[v][0]);
        //如果左儿子节点数>w,说明当前点(包括当前点右儿子)属于右树,
        //v为now,目标找到v的左儿子,把now的左儿子弄出去,u什么都没干
        else
        u = now, split(son[now][1], w - size[son[now][0]] - 1, son[u][1], v);
        //否则说明当前点(包括当前左儿子)属于左树,
        //u为now,目标找到u的右儿子,把now的右儿子弄出去,v什么都没干
        //别忘了把w减去左子树size+1
        pushup(now);
    }
}
merge
int merge(int u, int v){//合并两棵树,两棵树当前点为u、v
    if (!u || !v) return u + v;//如果两点有点为空点,返回另一个非空点
    if (key[u] >= key[v]){//满足大根堆性质,key[u]>=key[v]---->v接到u右儿子上
        pushdown(u);//不要忘了pushdown
        son[u][1] = merge(son[u][1], v);//左树节点在前
        pushup(u);//不要忘了pushup
        return u;//返回值有讲究,建议理解一下
    } else{//否则,把u接到v左儿子上,与上面同理
        pushdown(v);
        son[v][0] = merge(u, son[v][0]);
        pushup(v);
        return v;
    }
}

3、提升

pushup

理解pushup,就像线段树里的pushup一样,可以同时维护size,sum等等

pushdown

fhq其实也是可以pushdown的,各种tag都可以上
翻转tag, a d d add addtag, m u l t i p l y multiply multiplytag等等
主要就是split里面,merge里面都需要

  • 先pushdown
  • 操作
  • 最后pushup

这样一来,区间问题只要按照size,先把区间分裂出来,再干要干的事(比如打标记等等,就行了)

4、模板题

普通平衡树

Code:

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
int rt, sz, size[maxn], val[maxn], key[maxn], son[maxn][2];

inline int read(){
    int s = 0, w = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
    for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
    return s * w;
}

int addnode(int x){
    ++sz;
    val[sz] = x, key[sz] = rand() * rand(), size[sz] = 1;
    return sz;
}

void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + 1; }

void split(int now, int w, int &u, int &v){
    if (!now) u = v = 0; else{
        if (val[now] <= w ) u = now, split(son[now][1], w, son[u][1], v); 
		else
        v = now, split(son[now][0], w, u, son[v][0]);
        pushup(now);
    }
}

int merge(int u, int v){
    if (!u || !v) return u + v;
    if (key[u] <= key[v]){
        son[v][0] = merge(u, son[v][0]);
        pushup(v);
        return v;
    } else{
        son[u][1] = merge(son[u][1], v);
        pushup(u);
        return u;
    }
}

int kth(int now, int k){
    while (1){
        if (size[son[now][0]] >= k) now = son[now][0]; else
        if (size[son[now][0]] + 1 >= k) return now; else
        k -= size[son[now][0]] + 1, now = son[now][1];
    }
}

int main(){
    srand(time(0));
    int M = read();
    int x, y, z;
    while (M--){
        int opt = read(), k = read();
        if (opt == 1){
            split(rt, k, x, y);
            rt = merge(merge(x, addnode(k)), y);
        } else
        if (opt == 2){
            split(rt, k, x, y); split(x, k - 1, x, z);
            rt = merge(merge(x, merge(son[z][0], son[z][1])), y);
        } else
        if (opt == 3){
            split(rt, k - 1, x, y);
            printf("%d\n", size[x] + 1);
            rt = merge(x, y);
        } else
        if (opt == 4) printf("%d\n", val[kth(rt, k)]); else
        if (opt == 5){
            split(rt, k - 1, x, y);
            printf("%d\n", val[kth(x, size[x])]);
            rt = merge(x, y);
        } else{
            split(rt, k, x, y);
            printf("%d\n", val[kth(y, 1)]);
            rt = merge(x, y);
        }
    }
    return 0;
}
文艺平衡树

Code:

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
int sz, rt, val[maxn], key[maxn], son[maxn][2], size[maxn], n, m, tag[maxn];

inline int read(){
    int s = 0, w = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
    for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
    return s * w;
} 

int addnode(int x){
    ++sz;
    val[sz] = x, key[sz] = rand() * rand(), size[sz] = 1;
    return sz;
}

void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + 1; }

void pushdown(int x){
    if (tag[x]){
        tag[son[x][0]] ^= 1 ,tag[son[x][1]] ^= 1;
        swap(son[x][0], son[x][1]);
        tag[x] = 0;
    }
}

void split(int now, int w, int &u, int &v){
    if (!now) u = v = 0; else{
        pushdown(now);
        if (size[son[now][0]] >= w) v = now, split(son[now][0], w, u, son[v][0]); else
        u = now, split(son[now][1], w - size[son[now][0]] - 1, son[u][1], v);
        pushup(now);
    }
}

int merge(int u, int v){
    if (!u || !v) return u + v;
    if (key[u] >= key[v]){
        pushdown(u);
        son[u][1] = merge(son[u][1], v);
        pushup(u);
        return u;
    } else{
        pushdown(v);
        son[v][0] = merge(u, son[v][0]);
        pushup(v);
        return v;
    }
}

void write(int now){
    if (!now) return;
    pushdown(now);
    write(son[now][0]);
    printf("%d ", val[now]);
    write(son[now][1]);
}

int main(){
    srand(time(0));
    n = read(), m = read();
    for (int i = 1; i <= n; ++i) rt = merge(rt, addnode(i));
    while (m--){
        int l = read(), r = read(), x, y, z;
        split(rt, r, x, y);
        split(x, l - 1, x, z);
        tag[z] ^= 1;
        rt = merge(merge(x, z), y);
    }
    write(rt);
    return 0;
}

5、总结

fhq又简单,又实用
爱死了!!
split灵活多变
split好以后不要忘了merge

6、习题

[NOI2004]郁闷的出纳员
送花
宝石管理系统
序列终结者
[HNOI2002]营业额统计
[POI2008]KLO-Building blocks
[APIO2015]八邻旁之桥
[HNOI2004]宠物收养场

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值