可持久化线段树学习笔记
可持久化数据结构初探
操作
请设计一个支持如下操作的数据结构:
- 扩大这个数据结构的范围(如从 扩大到 ,保证最终的大小不会超过 )
- 在最新版本基础上修改某个位置上的值
- 查询最新版本某个位置上的值
- 回滚到某一个版本
- 在某一个版本基础上修改某个位置的值
- 查询某一个版本某个位置上的值
为了方便,初始时数据结构范围为 ;输入数据均为正整数;每次修改时,都要新建一个版本(基于哪一个版本取决于操作的编号);每次查询时,新建一个与最新版本相同的版本
解析
前置条件:
- 数组下标从 1 开始
- 一开始就直接开到最大数据结构大小
朴素算法 1
将版本视为一个时间轴,建立一个二维数组,第一维表示版本,第二维存对应版本的数据,每一次操作时把基于的版本复制一遍,然后修改、查询。
时空复杂度 ,其中 为数据结构最大大小, 为数据结构最大版本数
朴素算法 2
依然将版本视为一个时间轴,建立意义与朴素算法 1 相同的二维数组,不同的是,这次不再复制整个数组,取而代之的是在没有用到的下标 0 处记录这个版本是基于哪个版本的,修改只需修改对应的元素即可。查询时,先看这个元素是不是 0(意味着未被修改),是则跳到下标 0 记录的版本,重复这个过程,直到元素非 0,输出即可
空间复杂度 ;
修改时间复杂度 ,查询时间复杂度最坏为 ;
意义同朴素算法 1。
可持久化线段树
为什么要说算法 2 呢?
我个人认为,算法 2 只修改有用的点的特性正符合可持久化线段树的特点。
顺着算法 2 想下去。发现算法 2 有大量无用的空间,怎么办?
搬出树形结构,再加动态开点。
树的结构及修改操作
一棵线段树长这样:
然后发现修改第二个元素需要经历这些节点:
依据算法 2,开一些新的点作为这个版本的点
蓝点为原来的点,橙点为这个版本经过的点
可以发现它不再是一棵树。但是如果不看那些“被修改”的蓝点,它仍然是一棵二叉树……怎么办?
不妨把它视为多棵线段树,每一棵树都是一个版本,最顶上的依然是树根,只不过这些线段树共享了一些相同的节点。
具体实现的话,就把线段树的根节点们存在一个数组root[]
中,这样,root[i]
就表示第 i 棵(第 i 个版本的)线段树的根节点编号。
看一眼代码吧:
1 |
|
1 |
|
查询操作
明白了 root[]
数组的意义,查询就很简单了
比如说查询版本 1(初始版本为 0),就是顺着版本 1 的根节点往下找对应位置,查到底部返回。图中灰色的节点是查版本 1 永远碰不到的节点——因为它们在版本 1 中被修改了,而剩下的点依然形成了一棵线段树,顺着这棵树查下去就完事了
1 |
|
杂项
扩大数据结构范围
这个其实不用管。。。毕竟一开始就已经把范围开好了
回滚到某个版本
对线段树的具体内容没有一点操作,直接把新版本的根节点编号设为回滚到的版本
1 |
|
关于数据范围
由于线段树的空间开销本身就很大,再加上可持久化的节点,一般开到原数据范围的 32 倍(即MAXN << 5
)
代码实现
题目:洛谷P3919《【模板】可持久化数组》
支持的操作:操作 2、3、5、6
1 |
|
- 本文作者:Handwer STD
- 本文链接:https://blog.handwer-std.top/2019-11-03/Persistent-Segment-Tree/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!