# 浅谈 fhq-treap(无旋 treap)
模板题:洛谷 P6136 【模板】普通平衡树(数据加强版)
(emm 刚写了这个,就放这个吧)
也叫做 无旋 treap,由范浩强大佬发明。
个人认为是平衡树中码量最少,也最容易理解的一种写法。
# 主要思想
顾名思义,无旋意味着它没有旋转操作(终于没有恶心人的旋转了)。
事实上,无旋 treap 只有两种操作。
一种是 (分裂),另一种是 (合并)。
平衡树中的各种操作都可以用这两个函数来搞定。下面我们来一一分析。
# 常见操作
插入一个整数
删除一个整数 (若有多个相同的数,只删除一个)。
查询整数 的排名(排名定义为比当前数小的数的个数 +1)。
查询排名为 的数(如果不存在,则认为是排名小于 的最大数。保证 不会超过当前数据结构中数的总数)。
求 的前驱(前驱定义为小于 ,且最大的数)。
求 的后继(后继定义为大于 ,且最小的数)。
当然区间反转也是可以用无旋 实现的,这里先不讲。
# 函数解释
# split(分裂)
函数要实现把一棵平衡树拆成两棵,左边的树上节点的值都小于等于 , 右边的树上节点的值都大于等于 。
当前子树根节点
以 为分界线分割树
分裂之后两棵子树的根,左边的子树根是 ,右边的根是 ,这里要传地址,方便写。
注意: 当根为 0 时,一定一定一定要清空 ,&b&,不然会死循环(我就在这里卡了好久 QWQ)
inline void split(int x, int k, int &a, int &b){ | |
if(!x){ | |
a = b = 0; // 清空清空清空(重要的事情说三遍) | |
return; | |
} | |
if(t[x].val <= k){ | |
a = x; // 左边子树已经分裂出去了,直接 a=x | |
split(rs(x), k, rs(x), b); // 在右子树上查找,把拆下来右子树上节点值 & lt;=k 的点放回到 x 的右子树上 | |
}else{ | |
b = x; // 同理 | |
split(ls(x), k, a, ls(x)); | |
} | |
pushup(x); | |
} |
# merge(合并)
函数要实现把两个分开的树再合并到一起去,并返回合并后的根节点。
- 两棵树的根
这个操作类似于线段树合并。
注意:合并时只能是两个相邻的子树合并,不能跳着合并。
例如:树 ---> 子树 和 子树
子树 ---> 子树 和 子树 (---> 表示拆成两棵树)
那么我们合并时,只能 或 。
而不能
inline int merge(int x, int y){ | |
if(!x || !y) return x + y; | |
if(t[x].wei <= t[y].wei){ //wei 是随机值,wei (x) <= wei (y) 时,让 y 作为 x 的子树 | |
rs(x) = merge(rs(x), y); | |
pushup(x); | |
return x; | |
}else{ // 反之,x 作为 y 的子树 | |
ls(y) = merge(x, ls(y)); | |
pushup(y); | |
return y; | |
} | |
} |
# insert(插入)
先新建一个点设为 ,把原树拆成 和 。
中节点值 , 中节点值 。
然后一次合并 ,, 即可。
inline void insert(int k){ | |
t[++tot].val = k, t[tot].wei = rand(); | |
t[tot].siz = 1; | |
split(root, k, a, b); | |
root = merge(merge(a, tot), b); | |
} |
# remove(删除)
这个就很巧妙了,我们把值 的树先拆出来设为 ,然后把值 的点再拆出来,剩余的部分设为为 。
那么现在 中的点就是 的。
我们直接合并 的左右两棵子树,这样就相当于把根节点删了。
最后合并回去。
inline void remove(int k){ | |
split(root, k, a, b); // 拆,<=k 的存到 a 里,>k 的存到 b 里。 | |
split(a, k - 1, a, c); // 再拆,<k 的存到 a 里,=k 的存到里。 | |
c = merge(ls(c), rs(c)); // 合并 c 左右子树 | |
root = merge(merge(a, c), b); // 再把 a,b 合并上去。 | |
} |
# check_rk
查询 的排名。
我们把 的的点从树上分裂出来,然后分出来的树的大小 +1 就是排名。
别忘了合并回去。
inline int check_rk(int k){ | |
int rank; | |
split(root, k - 1, a, b); | |
rank = t[a].siz + 1; | |
root = merge(a, b); | |
return rank; | |
} |
# check_val
查询排名为 的数。
我这里用的递归写法。
当前子树的根。
在当前子树中排名多少。
我这里用的递归写法。(代码应该挺好理解的吧)
inline int check_val(int x, int k){ | |
if(k == t[ls(x)].siz + 1) return t[x].val; | |
if(k <= t[ls(x)].siz) return check_val(ls(x), k); // 在左子树中,直接跑左子树里查 | |
else return check_val(rs(x), k - t[ls(x)].siz - 1); // 在右子树中,减去左子树大小,再减 1(根),就是在右子树中的排名。 | |
} |
# check_pre
查询前驱。
这个也好说,我们先查排名,设排名为 ,我们再差排名为 的数是多少,就是前驱了。
inline int check_pre(int x){ | |
return check_val(root, check_rk(x) - 1); | |
} |
# check_next
查询 的后继,基本同理,但略有不同。
因为 可能有多个,我们查 的排名后 +1,不一定是下一个数,有可能还是 。
那我们怎么办呢?
其实也不难,我们直接查 的排名,没有?没关系,就算没有 ,查出来的也是大于 的最小的数的排名。
然后我们再查一下这个排名的值即可。
inline int check_next(int x){ | |
return check_val(root, check_rk(x + 1)); | |
} |
然后…… 就没有然后了吧。
主函数我就不用多说了吧。
# 完整代码
#include <iostream> | |
#include <cstdio> | |
#include <algorithm> | |
#include <cstdlib> | |
#define ls(x) t[x].ch[0] | |
#define rs(x) t[x].ch[1] | |
using namespace std; | |
const int N = 2e6; // 原本的数 + 操作数。最多有 1.1e6 个数 | |
struct Tree{ | |
int wei, val, siz, ch[2]; | |
}t[N]; | |
int n, m, tot, root; | |
int last, ans, a, b, c; | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();} | |
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
inline void pushup(int x){ | |
t[x].siz = t[ls(x)].siz + t[rs(x)].siz + 1; | |
} | |
inline void split(int x, int k, int &a, int &b){ | |
if(!x){ | |
a = b = 0; | |
return; | |
} | |
if(t[x].val <= k){ | |
a = x; | |
split(rs(x), k, rs(x), b); | |
}else{ | |
b = x; | |
split(ls(x), k, a, ls(x)); | |
} | |
pushup(x); | |
} | |
inline int merge(int x, int y){ | |
if(!x || !y) return x + y; | |
if(t[x].wei <= t[y].wei){ | |
rs(x) = merge(rs(x), y); | |
pushup(x); | |
return x; | |
}else{ | |
ls(y) = merge(x, ls(y)); | |
pushup(y); | |
return y; | |
} | |
} | |
inline void insert(int k){ | |
t[++tot].val = k, t[tot].wei = rand(); | |
t[tot].siz = 1; | |
split(root, k, a, b); | |
root = merge(merge(a, tot), b); | |
} | |
inline void remove(int k){ | |
split(root, k, a, b); | |
split(a, k - 1, a, c); | |
c = merge(ls(c), rs(c)); | |
root = merge(merge(a, c), b); | |
} | |
inline int check_rk(int k){ | |
int rank; | |
split(root, k - 1, a, b); | |
rank = t[a].siz + 1; | |
root = merge(a, b); | |
return rank; | |
} | |
inline int check_val(int x, int k){ | |
if(k == t[ls(x)].siz + 1) return t[x].val; | |
if(k <= t[ls(x)].siz) return check_val(ls(x), k); | |
else return check_val(rs(x), k - t[ls(x)].siz - 1); | |
} | |
inline int check_pre(int x){ | |
return check_val(root, check_rk(x) - 1); | |
} | |
inline int check_next(int x){ | |
return check_val(root, check_rk(x + 1)); | |
} | |
int main(){ | |
n = read(), m = read(); | |
for(int i = 1; i <= n; i++){ | |
int x; | |
x = read(); | |
insert(x); | |
} | |
while(m--){ | |
int op, x; | |
op = read(), x = read() ^ last; | |
if(op == 1) insert(x); | |
if(op == 2) remove(x); | |
if(op <= 2) continue; | |
if(op == 3) last = check_rk(x); | |
if(op == 4) last = check_val(root, x); | |
if(op == 5) last = check_pre(x); | |
if(op == 6) last = check_next(x); | |
ans ^= last; | |
} | |
printf("%d\n", ans); | |
return 0; | |
} |