可持久化平衡树
前置知识¶
OI 常用的可持久化平衡树 一般就是 可持久化无旋转 Treap 所以推荐首先学习 无旋转 Treap。
思想/做法¶
对于非旋转 Treap,可通过 Merge 和 Split 操作过程中复制路径上经过的节点(一般在 Split 操作中复制,确保不影响以前的版本)就可完成可持久化。
对于旋转 Treap,在复制路径上经过的节点同时,还需复制受旋转影响的节点(若其已为这次操作中复制的节点,则无需再复制),对于一次旋转一般只影响两个节点,那么不会增加其时间复杂度。
上述方法一般被称为 path copying。
「一切可支持操作都可以通过 Merge Split Newnode Build 完成」,而 Build 操作只用于建造无需理会,Newnode(新建节点)就是用来可持久化的工具。
我们来观察一下 Merge 和 Split,我们会发现它们都是由上而下的操作!
因此我们完全可以 参考线段树的可持久化操作 对它进行可持久化。
可持久化操作¶
可持久化 是对 数据结构 的一种操作,即保留历史信息,使得在后面可以调用之前的历史版本。
对于 可持久化线段树 来说,每一次新建历史版本就是把 沿途的修改路径 复制出来
那么对可持久化 Treap(目前国内 OI 常用的版本)来说:
在复制一个节点
- 如果某个儿子节点
Y X_{a+1} Y_{a} Y a - 反之,如果要修改
Y Y_{a+1} Y a+1 X_{a+1} Y_{a+1} Y a+1
可持久化¶
需要的东西:
-
一个
struct
数组 存 每个节点 的信息(一般叫做tree
数组);(当然写 指针版 平衡树的大佬就可以考虑不用这个数组了) -
一个 根节点数组,存每个版本的树根,每次查询版本信息时就从 根数组存的节点 开始;
-
split()
分裂 从树中分裂出两棵树 -
merge()
合并 把两棵树按照随机权值合并 -
newNode()
新建一个节点 -
build()
建树
Split¶
对于 分裂操作,每次分裂路径时 新建节点 指向分出来的路径,用 std::pair
存新分裂出来的两棵树的根。
split(x,k)
返回一个 std::pair
;
表示把
- 如果
x key \geq k x - 否则递归 右子树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | static std::pair<int, int> _split(int _x, int k) {
if (_x == 0)
return std::make_pair(0, 0);
else {
int _vs = ++_cnt; // 新建节点(可持久化的精髓)
_trp[_vs] = _trp[_x];
std::pair<int, int> _y;
if (_trp[_vs].key <= k) {
_y = _split(_trp[_vs].leaf[1], k);
_trp[_vs].leaf[1] = _y.first;
_y.first = _vs;
} else {
_y = _split(_trp[_vs].leaf[0], k);
_trp[_vs].leaf[0] = _y.second;
_y.second = _vs;
}
_trp[_vs]._update();
return _y;
}
}
|
Merge¶
merge(x,y)
返回 merge 出的树的根。
同样递归实现。如果 x 的随机权值>y 的随机权值,则 merge(x_{rc},y)
,否则 merge(x,y_{lc})
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static int _merge(int _x, int _y) {
if (_x == 0 || _y == 0)
return _x ^ _y;
else {
if (_trp[_x].fix < _trp[_y].fix) {
_trp[_x].leaf[1] = _merge(_trp[_x].leaf[1], _y);
_trp[_x]._update();
return _x;
} else {
_trp[_y].leaf[0] = _merge(_x, _trp[_y].leaf[0]);
_trp[_y]._update();
return _y;
}
}
}
|
例题¶
洛谷 P3835【模版】可持久化平衡树
你需要实现一个数据结构,要求提供如下操作(最开始时数据结构内无数据):
- 插入
x - 删除
x - 查询
x - 查询排名为
x - 求
x x -2\,147\,483\,647 - 求
x x 2\,147\,483\,647
以上操作均基于某一个历史版本,同时生成一个新的版本(操作 3, 4, 5, 6 即保持原版本无变化)。而每个版本的编号则为操作的序号。特别地,最初的版本编号为 0。
就是 普通平衡树 一题的可持久化版,操作和该题类似。
只是使用了可持久化的 merge 和 split 操作。
推荐的练手题¶
build本页面最近更新:,更新历史
edit发现错误?想一起完善? 在 GitHub 上编辑此页!
people本页面贡献者:OI-wiki
copyright本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用