本文讲述的是 可持久化权值线段树 。这种数据结构在普通线段树的基础之上支持查询某个历史版本,同时时间复杂度与线段树是同级,空间复杂度相较而言更高一些。
下文中将以「可持久化线段树」代指「可持久化权值线段树」。
注意:可持久化线段树难以进行区间修改操作,因为进行该操作可能会修改历史信息导致查询出错误结果,或者使用标记永久化避免出现这个问题,该项内容不在本文的讨论范围内。
#前置知识
#原理
可持久化线段树维护的是每个节点的历史版本,而最朴素的实现方式是每次进行修改操作都新开一颗线段树,而这样操作带来的时空复杂度是不可接受的。
稍微推导一下可以发现每次修改操作需要修改的节点个数只有 个,那么可以使用动态开点并记录历史版本的方式来节约空间和时间的消耗,这就是可持久化线段树。
#实现
先进行离散化,以节约存储空间,具体实现不再过多描述。
然后以数值为基础建立权值线段树,维护每个数值区间中一共有多少个数。
node
类型的定义如下:
1 |
|
这和 线段树学习笔记 中的定义有一些差别。首先,l
和 r
的存储的信息从左右端点变成了左右儿子的数组下标;其次,区间维护的信息从节点的和差与最值变成了区间中数据的个数。
再来看 build 函数。
1 |
|
变化是线段树存储方式从原先的堆变成了动态开点,动态开点线段树可以在网上找到相关学习资料,在此不作过多赘述。
还有 insert
函数:
1 |
|
仍然是动态开点,新节点的初始值由 node(l, r)
变成了 tr[p]
,即以第 个版本为基础,再进行修改。
最后是 query
函数:
1 |
|
通过 tr[tr[q].l].c - tr[tr[p].l].c
获得 区间内数的个数。
注:第 号节点的数的个数减去第 号节点的数的个数即为 中的数的个数,这里使用到了前缀和的思想。
取 区间的数的个数并记为 ,接下来分两种情况:
- :第 小的数在左子树上。
- :第 小的数在右子树上,查询右子树第 小的数。
递归查询即可。
#代码
对应题目:洛谷 - P3834 【模板】可持久化线段树 2 8d70723
1 |
|