fhqTreap
为什么要学
f
h
q
T
r
e
a
p
fhqTreap
fhqTreap 呢,写
s
p
l
a
y
splay
splay 她不好吗qwq
f h q T r e a p fhqTreap fhqTreap 作为一种平衡树,她有很多优势,最重要的就是她好理解,码量小,而且实测下来还比 s p l a y splay splay 快,最重要的是她不用旋转!!!
所以我们来学 f h q T r e a p fhqTreap fhqTreap 吧qwq。
关于 split & merge
和正常的 t r e a p treap treap 一样,这玩意儿的每个点上也有两个权值,一个是二叉查找树的权值 t r e e tree tree,另一个是堆的优先级 h e a p heap heap。
然后我们就会考虑如何同时维护二叉查找树的性质和堆的性质,于是这里就有了两个在 f h q T r e a p fhqTreap fhqTreap 里面非常重要的函数,也就是 s p l i t ( ) split() split() 和 m e r g e ( ) merge() merge()
顾名思义, s p l i t ( ) split() split() 就是把一棵树分成两半,而 m e r g e ( ) merge() merge() 就是把两棵树合并起来,首先我们先感受一下有了这两个操作之后剩下的东西会变得多么简单吧。
split(int k)
首先, s p l i t ( k ) split(k) split(k) 表示把一颗 f p q T r e a p fpqTreap fpqTreap 按照权值 k k k 划分成两部分,就比如我们对下图中的树进行 s p l i t ( 6 ) split(6) split(6) 的操作,就会变成这样(这里标的每个点的权值都是 t r e e tree tree 值而没有标 h e a p heap heap 值):
可以这样感性理解一下,就是把小于等于 6 6 6 的弄成一颗 t r e a p treap treap,大于 6 6 6 的弄成另一颗 t r e a p treap treap,那么有了这个东西和 m e r g e ( ) merge() merge()( m e r g e merge merge 应该不用多说了吧,就是把分开的俩玩意儿合起来) 之后,其他的操作就会变得非常简单了:
这里做一些规定,我们令 s p l i t ( ) split() split() 之后权值较小的树放在左边,后文会用左边的树或者 x x x 来指代她,令权值较大的树放在右边,后文会用右边的树或者 y y y 来指代她。并且令树 x x x 的根为 r o o t ( x ) root(x) root(x)。
insert(int x)
首先是插入操作,比如说我们对下面这棵树进行 i n s e r t ( 12 ) insert(12) insert(12) 的操作应该怎么做呢。
非常简单啊,直接 s p l i t ( 12 ) split(12) split(12),然后把 12 12 12 接到左边的那颗树上,因为左边的都比 12 12 12 小,所以直接接到最右边就可以了,然后再把两棵树 m e r g e ( ) merge() merge() 起来就好了,是不是很简单呢,就像这样:
注意这里是没有考虑 h e a p heap heap 值的问题,但是可以感性理解一下, i n s e r t ( ) insert() insert() 工作就是这样完成的了。
delete(int x)
众所周知 s p l a y splay splay 的 d e l e t e ( ) delete() delete() 真的是非常复杂,但是这里 f h q T r e a p fhqTreap fhqTreap 的删除操作就非常简单了,我们考虑进行 d e l e t e ( k ) delete(k) delete(k) 时直接 s p l i t ( k ) split(k) split(k),然后会发现 x x x 树上的值都有 v a l ≤ k val \leq k val≤k,然后我们考虑把 k k k 单独分出来,于是我们对于 x x x 树再 s p l i t ( k − 1 ) split(k - 1) split(k−1),这样就分成了 x x xx xx 树和 x y xy xy 树,并且 x y xy xy 树只有一个节点,并且这个节点的权值也就是 k k k。然后直接就不管 x y xy xy 把 x x xx xx 和 y y y m e r g e ( ) merge() merge() 起来就好了。
下面的图解以 d e l e t e ( 13 ) delete(13) delete(13) 为例(还是之前 i n s e r t ( ) insert() insert() 的时候用过的那颗树):
getRankbyVal(int x)
按值查排名,直接 s p l i t ( x − 1 ) split(x - 1) split(x−1) 然后再返回这棵树的 s i z e + 1 size+1 size+1 就好了。
getValbyRank(int k)
这个似乎只能像正常的平衡树那样写了~~~
prefix(int x)
查前驱的话直接 s p l i t ( x − 1 ) split(x - 1) split(x−1) 然后返回 x x x 树中最大的那个值,也就是在最右边的值,然后再 m e r g e ( ) merge() merge() 回去就可以了。
sufix(int x)
查后继的话直接 s p l i t ( x ) split(x) split(x) 然后返回在 y y y 树中最小的那个值,也就是最左边的值,然后再 m e r g e ( ) merge() merge() 回去就可以了。
怎么样,看完之后是不是感觉非常简单,除了按排名插值之外,所有的操作都有了自己的比普通的平衡树更简洁并且更易懂的写法。是不是感觉非常爽,那么接下来我们就来看看如何实现 s p l i t ( ) split() split() 和 m e r g e ( ) merge() merge() 操作吧。
实现 split & merge
split(int p, int k, int &x, int &y)
这个 split(int p, int k, int &ls, int &rs) \text{split(int p, int k, int \&ls, int \&rs)} split(int p, int k, int &ls, int &rs) 函数就是上文说的按照 k k k 分裂,只是上文中为了简单而把其他的几个变量省略了。
p p p 就表示当前节点, l s ls ls 和 r s rs rs 是需要修改的两个量也就是上一个当前节点的左儿子和右儿子。
那么我们从根结点出发,对于遇到的每一个点,如果这个点的权值小于等于 k k k,那么就进到左子树里并且修改 l s = p ls = p ls=p,大于 k k k 就进入右子树,并修改 r s = p rs = p rs=p,然后递归处理子树就好了:
void split(int p, int k, int &x, int &y){
if(!p) x = y = 0;
else{
if(val[p] <= k) x = p, split(rs(p), k, rs(x), y);
else y = p, split(ls(p), k, x, ls(y));
up(p);
}
}
merge(int x, int y)
把两个 T r e a p Treap Treap 合并成一个的函数,并且保证 x x x 树的权值小于 y y y 树。这里就可以看成 s p l i t ( ) split() split() 的逆向操作就可以了:
int merge(int x, int y){
if(!x or !y) return x + y;
if(hep[x] < hep[y]){
ch[x][1] = merge(rs(x), y), up(x); return x;
}
else{
ch[y][0] = merge(x, ls(y)), up(y); return y;
}
}
代码
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 100100
#define ls(p) ch[p][0]
#define rs(p) ch[p][1]
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9')
x = x * 10 + c - '0', c = getchar();
return x;
}
int n = 0, root = 0;
int tot = 0;
int val[MAXN] = { 0 };
int hep[MAXN] = { 0 };
int siz[MAXN] = { 0 };
int ch[MAXN][2] = { 0 };
inline void up(int p) { siz[p] = siz[ls(p)] + siz[rs(p)] + 1; }
inline int cre(int v) { siz[++tot] = 1, val[tot] = v, hep[tot] = rand(); return tot; }
void split(int p, int k, int &x, int &y){
if(!p) x = y = 0;
else{
if(val[p] <= k) x = p, split(rs(p), k, rs(x), y);
else y = p, split(ls(p), k, x, ls(y));
up(p);
}
}
int merge(int x, int y){
if(!x or !y) return x + y;
if(hep[x] < hep[y]){
ch[x][1] = merge(rs(x), y), up(x); return x;
}
else{
ch[y][0] = merge(x, ls(y)), up(y); return y;
}
}
void ins(int k){
int x = 0, y = 0;
split(root, k, x, y);
root = merge(merge(x, cre(k)), y);
}
void del(int k){
int x = 0, y = 0, z = 0;
split(root, k, x, z), split(x, k - 1, x, y);
if(y) y = merge(ls(y), rs(y));
root = merge(merge(x, y), z);
}
int rkbyval(int k){
int x = 0, y = 0, ans = 0;
split(root, k - 1, x, y);
ans = siz[x] + 1;
root = merge(x, y);
return ans;
}
int valbyrk(int k){
int p = root;
while(114514){
if(siz[ls(p)] + 1 == k) break;
else if(siz[ls(p)] + 1 > k) p = ls(p);
else k -= siz[ls(p)] + 1, p = rs(p);
}
return val[p];
}
int pre(int k){
int x = 0, y = 0, p = 0, ans = 0;
split(root, k - 1, x, y); p = x;
while(rs(p)) p = rs(p);
ans = val[p];
root = merge(x, y);
return ans;
}
int suf(int k){
int x = 0, y = 0, p = 0, ans = 0;
split(root, k, x, y); p = y;
while(ls(p)) p = ls(p);
ans = val[p];
root = merge(x, y);
return ans;
}
int main(){
n = in; srand(time(0));
while(n--){
int op = in, x = in;
if(op == 1) ins(x);
if(op == 2) del(x);
if(op == 3) cout << rkbyval(x) << '\n';
if(op == 4) cout << valbyrk(x) << '\n';
if(op == 5) cout << pre(x) << '\n';
if(op == 6) cout << suf(x) << '\n';
}
return 0;
}