可持久化平衡树
可持久化无旋转 Treap
前置知识
OI 常用的可持久化平衡树 一般就是 可持久化无旋转 Treap 所以推荐首先学习 无旋转 Treap。
思想/做法
对于非旋转 Treap,可通过 Merge 和 Split 操作过程中复制路径上经过的节点(一般在 Split 操作中复制,确保不影响以前的版本)就可完成可持久化。
对于旋转 Treap,在复制路径上经过的节点同时,还需复制受旋转影响的节点(若其已为这次操作中复制的节点,则无需再复制),对于一次旋转一般只影响两个节点,那么不会增加其时间复杂度。
上述方法一般被称为 path copying。
「一切可支持操作都可以通过 Merge Split Newnode Build 完成」,而 Build 操作只用于建造无需理会,Newnode(新建节点)就是用来可持久化的工具。
我们来观察一下 Merge 和 Split,我们会发现它们都是由上而下的操作!
因此我们完全可以 参考线段树的可持久化操作 对它进行可持久化。
可持久化操作
可持久化 是对 数据结构 的一种操作,即保留历史信息,使得在后面可以调用之前的历史版本。
对于 可持久化线段树 来说,每一次新建历史版本就是把 沿途的修改路径 复制出来
那么对可持久化 Treap(目前国内 OI 常用的版本)来说:
在复制一个节点
- 如果某个儿子节点
不用修改信息,那么就把 的指针直接指向 ( 节点的第 个版本)即可。 - 反之,如果要修改
,那么就在 递归到下层 时 新建 ( 节点的第 个版本)这个新节点用于 存储新的信息,同时把 的指针指向 ( 节点的第 个版本)。
可持久化
需要的东西:
一个
struct
数组 存 每个节点 的信息(一般叫做tree
数组);(当然写 指针版 平衡树的大佬就可以考虑不用这个数组了)一个 根节点数组,存每个版本的树根,每次查询版本信息时就从 根数组存的节点 开始;
split()
分裂 从树中分裂出两棵树merge()
合并 把两棵树按照随机权值合并newNode()
新建一个节点build()
建树
Split
对于 分裂操作,每次分裂路径时 新建节点 指向分出来的路径,用 std::pair
存新分裂出来的两棵树的根。
split(x,k)
返回一个 std::pair
;
表示把
- 如果
的 左子树 的 ,那么 直接递归进左子树,把左子树分出来的第二颗树和当前的 右子树 合并。 - 否则递归 右子树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
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 |
|
可持久化 WBLT
前置知识
可持久化 WBLT 由 WBLT 改动而来,所以首先学习 WBLT。
思想/做法
使用 路径复制 的方法,将一次操作中 修改过 的节点复制下来,不能影响之前的节点。
处理懒标记
为了处理懒标记,我们这样考虑:在一棵持久化的 WBLT 上,一个点可能有多个父亲,但是儿子数量只能是
实现路径复制
在进行路径复制的时候,我们可以定义一个 refresh 函数,它接受一个节点
对于静态的查询,除了 pushdown 之外都不用 refresh。如果保证什么操作都做路径复制,那么 pushdown 和 refresh 的顺序是无所谓的。
针对持久化 WBLT 的小优化
这里有一个优化。观察到 pushdown 的时候要复制两个节点,可以写标记永久化,但是刚才说了,如果它的儿子只有它一个父亲,可以不用复制。针对这一个性质,可以进行优化,以减少复制多余的节点。
考虑记录每个节点有多少个父亲(认为每个版本的根都有一个父亲),记为
代码实现
完整代码(可持久化文艺平衡树)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
|
例题
洛谷 P3835【模版】可持久化平衡树
你需要实现一个数据结构,要求提供如下操作(最开始时数据结构内无数据):
- 插入
数; - 删除
数(若有多个相同的数,应只删除一个,如果没有请忽略该操作); - 查询
数的排名(排名定义为比当前数小的数的个数 + 1); - 查询排名为
的数; - 求
的前驱(前驱定义为小于 ,且最大的数,如不存在输出 ); - 求
的后继(后继定义为大于 ,且最小的数,如不存在输出 )。
以上操作均基于某一个历史版本,同时生成一个新的版本(操作 3, 4, 5, 6 即保持原版本无变化)。而每个版本的编号则为操作的序号。特别地,最初的版本编号为 0。
就是 普通平衡树 一题的可持久化版,操作和该题类似。
只是使用了可持久化的 merge 和 split 操作。
推荐的练手题
本页面最近更新:2024/4/21 20:41:05,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:Alpha1022, Backl1ght, caijianhong, cbioo, ChungZH, countercurrent-time, dkz051, Enter-tainer, H-J-Granger, Henry-ZHR, hly1204, Ir1d, isdanni, Marcythm, NachtgeistW, ouuan, sshwy, SukkaW, VirtualHawthorn
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用