FHQ-Treap,启动!(按值合并、分裂)
注明
小说作者 BestMonkey,由于 pb_ds 写平衡树写挂
(乐辉!你个gd看看啊!)(<-题外话)索性学习手写平衡树,如你所见,学的是牛牛牛的 FHQ-Treap!
参考文献:
- Treap(From:OI_Wiki);
- 【学习笔记】FHQ-Treap(From:博客园-KingPower)。
正文
为了方便讲解,选了题目 P3369 【模板】普通平衡树 为例题。
树的定义
定义 l _ s o n l\_{son} l_son 为左儿子编号, r _ s o n r\_son r_son 为右儿子编号。为了后续方便使用,加上了:
#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son
代码片段:
#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son
struct Porject_FHQ_Treap {
int l_son, r_son;
int val, key, sz;
} Treap[Size];
核心、基本操作
基本操作
建新的结点
注意键值随机。
跟着代码意会一下。
代码片段:
inline int New_Node(int val) {
Treap[++Treap_Len].val = val,
Treap[Treap_Len].key = rnd(),
Treap[Treap_Len].sz = 1;
return Treap_Len;
}
信息上传合并
信息上传合并其实很简单。显然,此时,当前节点的大小就是左儿子和右儿子的大小加上
1
1
1。
依然跟着代码意会一下。
代码片段:
inline void Push_Up(int now) {
Treap[now].sz =
Treap[lson(now)].sz + Treap[rson(now)].sz + 1;
}
核心操作
核心操作其实就只有分裂、合并这两个,搞懂之后自然可以知道插入、删除、查询排名等操作怎么写。
分裂
分裂操作会将整棵树分裂为两棵树 x x x 和 y y y,且 x x x 中的值全部小于等于给定的值, y y y 中的值全部大于给定的值。
代码片段:
inline void Split(int now, int val, int &x, int &y) { //将树按val分裂成两棵树,分别以x和y为根
if (!now) {//分到底了
x = y = 0;
return;
}
if (Treap[now].val <= val) { //如果当前的值小于给定的,那么根据二叉搜索树的性质,给定的值一定在右子树中
x = now; //确定其中一个根
Split(rson(now), val, rson(now), y);//去右子树分裂
} else {
y = now;
Split(lson(now), val, x, lson(now));
}
Push_Up(now);//儿子变了,更新
}
合并
合并操作会将两棵树
x
x
x 和
y
y
y 合并为一棵,前提条件为
x
x
x 内的所有值都小于等于y内的所有值。
显然,合并后的树仍然满足Treap的性质。
代码片段:
inline int Merge(int x, int y) {//将x和y合并为一棵树,并将合并后的根返回
if (!x || !y)
return x ^ y; //就是求x和y中不为0的那一个
if (Treap[x].key > Treap[y].key) { //这里默认大根堆
rson(x) = Merge(rson(x), y);
Push_Up(x);
return x;
} else {
lson(y) = Merge(x, lson(y));
Push_Up(y);
return y;
}
}
衍生的基本操作
其实学会上面的操作之后完全可以自己手写出插入、删除、查前驱等基本操作。
插入
假如要插入的值为
v
a
l
val
val,则可以按照
v
a
l
val
val 将原树分裂为
x
x
x 和
y
y
y。
根据上文的定义,
x
x
x 中的所有值一定都小于等于
y
y
y,因此可以直接将
x
x
x 和新结点合并,然后再重新与
y
y
y 合并即可。
代码片段:
inline void Insert(int val) {
Split(Root, val, x, y);
Root = Merge(Merge(x, New_Node(val)), y);
}
删除
假如要删除的值为
v
a
l
val
val,那么我们可以按照
v
a
l
val
val 将原树分裂为
x
x
x 和
y
y
y,然后再按照
v
a
l
−
1
val-1
val−1 将
x
x
x 再分裂为
x
x
x 和
z
z
z。
根据上文的定义,此时
z
z
z 中的值一定全都与
v
a
l
val
val 相等。此时我们可以去掉
z
z
z 的根节点,最后再都重新合并回去即可。
代码片段:
inline void Erase(int val) {
Split(Root, val, x, y),
Split(x, val - 1, x, z);
z = Merge(lson(z), rson(z)),
Root = Merge(Merge(x, z), y);
}
按值查找排名
设要查询的值为
v
a
l
val
val,那么我们可以按照
v
a
l
−
1
val-1
val−1 将原树分裂为
x
x
x 和
y
y
y,然后此时左子树的大小就是比
v
a
l
val
val 小的值的个数,再加上
1
1
1 就是答案。
注意最后要合并回去。
代码片段:
inline int Order_Of_Key(int val) {
Split(Root, val - 1, x, y);
int res = Treap[x].sz + 1;
Root = Merge(x, y);
return res;
}
按排名查找值
按照 BST 的性质可知:
- 若当前的左子树大小大于给定排名,答案就在左子树中,
- 否则就去右子树。
注意去右子树的话,要将查询的排名减去左子树的大小,再包括自己占的一个。
代码片段:
inline int Find_By_Order(int rk) {
int now = Root;
while (now) {
if (Treap[lson(now)].sz + 1 == rk)
break;
else if (Treap[lson(now)].sz >= rk)
now = lson(now);
else
rk -= Treap[lson(now)].sz + 1, now = rson(now);
}
return Treap[now].val;
}
找前驱
设要查询的值为
v
a
l
val
val,那么我们可以将原树按
v
a
l
−
1
val-1
val−1 分裂为
x
x
x 和
y
y
y,然后根据二叉搜索树的性质,我们在
x
x
x 中一直往右跳就可以找到前驱了。
在最后注意合并。
代码片段:
inline int Find_Pre(int val) {
Split(Root, val - 1, x, y);
int now = x, res;
while (rson(now))
now = rson(now);
res = Treap[now].val,
Root = Merge(x, y);
return res;
}
找后继
设要查询的值为 v a l val val,那么我们可以将原树按照 v a l val val 分裂为 x x x 和 y y y,然后根据BST的性质在y中一直往左跳就可以找到后继了。
代码片段:
inline int Find_Nxt(int val) {
Split(Root, val, x, y);
int now = y, res;
while (lson(now))
now = lson(now);
res = Treap[now].val, Root = Merge(x, y);
return res;
}
完整代码 (Damn) 按值分裂、合并
#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(233);
#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son
const int Size = 1e5 + 5;
struct Porject_FHQ_Treap {
int l_son, r_son;
int val, key, sz;
} Treap[Size];
int Treap_Len, Root, x, y, z;
inline int New_Node(int val) {
Treap[++Treap_Len].val = val,
Treap[Treap_Len].key = rnd(),
Treap[Treap_Len].sz = 1;
return Treap_Len;
}
inline void Push_Up(int now) {
Treap[now].sz =
Treap[lson(now)].sz + Treap[rson(now)].sz + 1;
}
inline void Split(int now, int val, int &x, int &y) {
if (!now) {
x = y = 0;
return;
}
if (Treap[now].val <= val) {
x = now;
Split(rson(now), val, rson(now), y);
} else {
y = now;
Split(lson(now), val, x, lson(now));
}
Push_Up(now);
}
inline int Merge(int x, int y) {
if (!x || !y)
return x ^ y;
if (Treap[x].key > Treap[y].key) {
rson(x) = Merge(rson(x), y);
Push_Up(x);
return x;
} else {
lson(y) = Merge(x, lson(y));
Push_Up(y);
return y;
}
}
inline void Insert(int val) {
Split(Root, val, x, y);
Root = Merge(Merge(x, New_Node(val)), y);
}
inline void Erase(int val) {
Split(Root, val, x, y),
Split(x, val - 1, x, z);
z = Merge(lson(z), rson(z)),
Root = Merge(Merge(x, z), y);
}
inline int Order_Of_Key(int val) {
Split(Root, val - 1, x, y);
int res = Treap[x].sz + 1;
Root = Merge(x, y);
return res;
}
inline int Find_By_Order(int rk) {
int now = Root;
while (now) {
if (Treap[lson(now)].sz + 1 == rk)
break;
else if (Treap[lson(now)].sz >= rk)
now = lson(now);
else
rk -= Treap[lson(now)].sz + 1, now = rson(now);
}
return Treap[now].val;
}
inline int Find_Pre(int val) {
Split(Root, val - 1, x, y);
int now = x, res;
while (rson(now))
now = rson(now);
res = Treap[now].val,
Root = Merge(x, y);
return res;
}
inline int Find_Nxt(int val) {
Split(Root, val, x, y);
int now = y, res;
while (lson(now))
now = lson(now);
res = Treap[now].val, Root = Merge(x, y);
return res;
}
signed main() {
int n;
cin >> n;
int opt, x;
while (n--) {
cin >> opt >> x;
if (opt == 1)Insert(x);
else if (opt == 2)Erase(x);
else if (opt == 3)cout << Order_Of_Key(x) << endl;
else if (opt == 4)cout << Find_By_Order(x) << endl;
else if (opt == 5)cout << Find_Pre(x) << endl;
else cout << Find_Nxt(x) << endl;
}
return 0;
}
启动? 按大小分裂、合并
注明
我的意思是看解析的时候别玩原神。
例题:文艺平衡树。
先解惑:为什么代码里有 Push_Down
(下传懒标记)的操作呢?(梦回线段树)
BlastMike:因为文艺平衡树这题比较特殊,不是简单的维护区间,有翻转,所以用懒标记标识是否要执行翻转操作。学过线段树总该明白吧。
核心操作
为什么只有核心操作的解读呢?因为其他操作都和之前一样(建议对前面的内容详细阅读)。
分裂
分裂操作其实就是把之前按值分裂的分裂操作中的比较值的大小改为比较大小(比较抽象。完了这下更抽象了。)。
注意不要写成:
if (Treap[now].sz < sz)
这相当于自己和自己比大小。(因为这个一个憨憨调了一个下午)
代码片段:
inline void Split(int now, int sz, int &x, int &y) {
if (!now) {
x = y = 0;
return;
}
Push_Down(now);
if (Treap[lson(now)].sz < sz) {
x = now;
Split(rson(now), sz - Treap[lson(now)].sz - 1, rson(now), y);
} else {
y = now;
Split(lson(now), sz, x, lson(now));
}
Push_Up(now);
}
合并
想不到吧,和之前一样吗?
代码片段:
inline int Merge(int x, int y) {
if (!x || !y)
return (x == 0 ? y : x);
if (Treap[x].key < Treap[y].key) {
Push_Down(x),
rson(x) = Merge(rson(x), y),
Push_Up(x);
return x;
} else {
Push_Down(y),
lson(y) = Merge(x, lson(y)),
Push_Up(y);
return y;
}
}
完整代码(按大小分裂、合并)
#include<bits/stdc++.h>
using namespace std;
#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son
mt19937 rnd(233);
const int Size = 100000 + 5;
struct Porject_FHQ_Treap {
int l_son, r_son;
int val, key, sz, ly_tag;
} Treap[Size];
int Treap_Len, Root;
inline int New_Node(int val) {
Treap[++Treap_Len].val = val,
Treap[Treap_Len].sz = 1,
Treap[Treap_Len].key = rnd();
return Treap_Len;
}
inline void Push_Up(int now) {
Treap[now].sz =
Treap[lson(now)].sz + Treap[rson(now)].sz + 1;
}
inline void Push_Down(int now) {
if (Treap[now].ly_tag)
swap(lson(now), rson(now)),
Treap[lson(now)].ly_tag ^= 1,
Treap[rson(now)].ly_tag ^= 1,
Treap[now].ly_tag = 0;
}
inline void Split(int now, int sz, int &x, int &y) {
if (!now) {
x = y = 0;
return;
}
Push_Down(now);
if (Treap[lson(now)].sz < sz) {
x = now;
Split(rson(now), sz - Treap[lson(now)].sz - 1, rson(now), y);
} else {
y = now;
Split(lson(now), sz, x, lson(now));
}
Push_Up(now);
}
inline int Merge(int x, int y) {
if (!x || !y)
return (x == 0 ? y : x);
if (Treap[x].key < Treap[y].key) {
Push_Down(x),
rson(x) = Merge(rson(x), y),
Push_Up(x);
return x;
} else {
Push_Down(y),
lson(y) = Merge(x, lson(y)),
Push_Up(y);
return y;
}
}
inline void Reverse(int l, int r) {
int x, y, z;
Split(Root, l - 1, x, y),
Split(y, r - l + 1, y, z);
Treap[y].ly_tag ^= 1;
Root = Merge(Merge(x, y), z);
}
inline void Print(int now) {
if (!now)
return;
Push_Down(now);
Print(lson(now)),
cout << Treap[now].val << " ",
Print(rson(now));
}
signed main() {
int n, m;
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0), cin >> n >> m;
for (register int i = 1; i <= n; ++i)
Root = Merge(Root, New_Node(i));
int l, r;
while (m--)
cin >> l >> r,
Reverse(l, r);
Print(Root);
return 0;
}
戛然而止。
片尾