Problem’s Website
Solution
没错,又是这道题。
再上一篇题解中,我们介绍了使用$Treap$的做法,那么本篇题解讲解一下$fhq-Treap$的做法。
先来普及一下,$fhq-Treap$由$OI$界的著名DALAO范浩强发明,至于他有多巨,看图吧。
他不仅$OI$强,人其实也挺 handsome 的。
P.s.该图片来源毕业季 | 范浩强:游走在学业和科研间的清华创客
好了,我们步入正题。
$fhq-Treap$又叫无旋Treap,顾名思义,就是没有旋转的$Treap$,取而代之的是分裂和合并,正因如此,$fhq-Treap$可以解决$Treap$解决不了的区间问题,而且还可以持久化,这些内容以后我会另外介绍。
首先,我们先明确下文中代码变量的意思。1
2
3
4int siz[Maxn], val[Maxn], dat[Maxn], ch[Maxn][2];
int x, y, z, tot, rt;
//siz为子树大小,val为节点权值,dat为rand值,ch记录左右儿子(0为左儿子,1为右儿子)。
// x,y,z为暂定的左右儿子编号,tot为总共的节点数,rt为当前树的根节点编号。
在说明基本操作前,我们先来说一下$fhq-Treap$的一些核心思想及操作
- 分裂
分裂分为两种,一种为权值分裂,另一种为排名分裂。
权值分裂
我们现在遍历到一个点,如果它的权值小于$v$,那么它的左子树会被分到左边的树里,然后我们遍历它的右儿子,如果大于$v$,则把它的右子树分到右边的树里。
那出现了一个小问题:如果到达递归边界id==0怎么办呢?
那我们就初始化一下,令$x = y = 0$
如果遍历到了叶子节点,我们就返回。
注意,对于形参中的$x,y$要取地址,这样就可以直接更新全局变量$x,y$。
看下代码吧1
2
3
4
5
6
7
8
9
10
11
12
13
14
15inline void split(int id, int v, int &x, int &y) {
if(!id)
x = y = 0;
else {
if(val[id] <= v) {
x = id;
split(ch[id][1], v, ch[id][1], y);
}
else {
y = id;
split(ch[id][0], v, x, ch[id][0]);
}
pushup(id);
}
}
排名分裂
与权值分裂类似,把权值当成排名(siz)即可。
code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15inline void split(int id, int r, int &x, int &y) {
if(!id)
x=y=0;
else {
if(r <= siz[ch[id][0]]) {
y = id;
split(ch[id][0], r, x, ch[id][0]);
}
else {
x = id;
split(ch[id][1], r - siz[ch[id][0]] - 1, ch[id][1], y);
}
pushup(id);
}
}
给个图感性理解一下:
- 合并
我们假设第一棵树的权值小于第二棵树的权值,那么我们就比较它们的随机权值。如果$dat[l] < dat[r]$,我们就保留它的左子树,另一棵树作为它的右子树;如果$dat[l] \ge dat[r]$,那我们可以保留第二棵树的右子树,另一颗树作为它的左子树。
code1
2
3
4
5
6
7
8
9
10
11
12
13
14inline int merge(int A, int B) {
if(!A || !B)
return A + B;
if(dat[A] < dat[B]) {
ch[A][1] = merge(ch[A][1], B);
pushup(A);
return A;
}
else {
ch[B][0] = merge(A, ch[B][0]);
pushup(B);
return B;
}
}
图示
现在我们开始介绍$fhq-Treap$的一些基本操作。
- $pushup$
合并子树大小。
code
1 | inline void pushup(int id) { |
- 插入
直接merge即可
code
1 | inline int cre(int v) { |
- 删除
删除权值为$v$的点,先把整颗树以$v$为权值分裂成两棵树$a,b$,再把$a$树按照$v - 1$分成$c,d$。这时候值为$v$的点一定为$d$的根,那么我们把$d$的两个子儿子merge起来(划重点:这一步就是去除掉v的影响),再把他们重新merge起来得到一个新的树,这颗树就去除掉了v的影响。
code1
2
3
4
5
6inline void Delete(int v) {
split(rt, v, x, z);
split(x, v - 1, x, y);
y = merge(ch[y][0], ch[y][1]);
rt = merge(merge(x, y), z);
}
- 得到排名
直接按照$v-1$的权值把树分开,那么$x$树中最大的应该小于等于$v-1$,那么$v$的排名就是$siz[x]+1$。
code1
2
3
4
5
6inline int get_rank(int v) {
split(rt, v - 1, x, y);
int res = siz[x] + 1;
rt = merge(x, y);
return res;
}
- 得到排名为几的数据
循环求解。
如果当前$r \le$左子树的大小,那么就往左子树找;如果$r$恰好等于左子树大小+ 1,说明就是当前节点;否则r减去(左子树大小 + 1),然后去找右子树。
code1
2
3
4
5
6
7
8
9
10
11
12
13
14inline int rank(int id, int r) {
while(1) {
if(r <= siz[ch[id][0]])
id = ch[id][0];
else if(r == siz[ch[id][0]] + 1)
return id;
else {
r -= (siz[ch[id][0]] + 1);
id = ch[id][1];
}
}
}
out(val[rank(rt, x)]), pc('\n'); //out为快输,pc为putchar。
- 前驱后驱
因为两者具有很大的相似性,所以我们只讲找前驱,后驱请读者根据代码自行理解。
因为要小于$v$,所以我们还是按照$v-1$的权值划分$x$,现在$x$中最大的数一定小于等于$v-1$,所以我们直接输出$x$中最大的数就好。
code1
2
3
4
5
6
7
8
9
10
11
12inline int get_pre(int v) { //前驱
split(rt, v - 1, x, y);
int res = val[rank(x, siz[x])];
rt = merge(x, y);
return res;
}
inline int get_next(int v) { //后驱
split(rt, v, x, y);
int res = val[rank(y, 1)];
rt = merge(x, y);
return res;
}
本题的操作就讲完了,当然$fhq-Treap$不止这些东西,以后根据题目再说明吧。
Code
1 |
|
rp++