“人最重要的,是不能忘记自己为何出发。”
可持久化数据结构
一种数据结构可持久化,当其满足本身的拓扑结构不变时,其可持久化。
因此,$\text{Trie}$ 树,线段树,树状数组,平衡树等都支持可持久化。
类似于 $\text{Github}$ 的
git
操作。——$\text{Yxc}$
可持久化数据结构用于支持存储该数据结构的所有历史版本。
假设有 $\mathcal O(m)$ 次操作,那么就会有 $m+1$ 个历史版本,如果每一个版本都去 $\text{copy}$ 一份的话,空间复杂度会高达 $\mathcal O(n(m+1))$ ,那么,可持久化的核心思想就是只去记录每一个版本被修改过的点,即与前一个版本不同的结点。
介绍
主席树全称可持久化权值线段树,用于维护区间最值与一些奇奇怪怪的东西。支持历史询问与修改(目前不会),似乎与普通线段树的时间复杂度是一样的,但空间会减少很多消耗。
在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本。这样的集合称为是可持久的。
实现
建树(build)
首先,建立起亘古树,即一开始的整棵线段树。但是根本不需要。对于主席树而言,改掉了我的一个习惯:即线段树的左区间与右区间是存于 $Tree$ 内的。这样可能会消耗一些空间。但对于主席树而言,其儿子节点并不满足 Tree[p].l=p<<1
与 Tree[p].r=p<<1|1
的关系,所以对于建树而言,每一个节点的 $l$ 和 $r$ 存储的是其左右儿子的点编号。每输入一个数,就动态修改即可,根本不需要进行 $build$ 操作。一般不建
修改/插入(modify)
每输入一个值……其实这句话是不对的。对于部分主席树的题而言,是需要离散化的。所以插入(修改)操作都是在输入完并离散化之后才一次性完成的。
以洛谷的模板题为例,我们需要求区间 $[l,r]$ 的第 $k$ 小值。则对于这道题的历史版本 $v_i$ ,处理的其实是前缀和的信息。即我们建的第 $i$ 个树存储的是区间 $[1,i]$ 的 $Val$ 信息。而叶节点存储的则是第 $i$ 个数的个数。
对于每一次的插入操作,我们就在原来基础上插入一条链直达叶节点,表示我们在该位置插入了一个数。然后自下而上更新区间值 $dat$ ,这与线段树是一样的。而不同的是,在插入过程中,其节点编号 $p$ 是动态的。因为当一个地方没有被修改时,我们会指向其历史编号,只有当修改时才会新建编号,而我们新建了编号后,其父节点所指的子节点也是会更改的。所以就会有 int &x
的情况。
1 | inline void underModify(int &x,int l,int r,int d) |
查询(query)
其意义并非真正的线段树查询。因为用了二分的思想又与线段树相似而称之为查询。对于查询第 $k$ 小数而言,因为我们已经离散化排了序的,则:
- 当该节点的左儿子存储的数的个数大于 $k$ ,则查询左儿子;
- 当该节点的左儿子存储的数的个数小于 $k$ ,则查询右儿子的第 $k-ls$ 小值。
这就因题而异了,只是提一句。
初始化
离散化
因题而异。可以有,也可以没有。
排序
因题而异。可以有,也可以没有。
修改根节点(root)
每一个历史线段树的根节点在一开始的时候都是空的,其会在插入操作里不断更新。这也是为什么 int &x
的原因(二次重提)
AC Code
查看代码
1 |
|
可持久化数组
其实差不多,只是多了一个建树操作。因为它有一个初始数列,这道题真的就是字面意思的可持久化了。
AC Code
查看代码
1 |
|