史上最短学习笔记,没有之一。
可持久化线段树
相信大家都会。核心思想是每次修改最多改变 \(\mathcal O(\log n)\) 个元素,因此每次修改只需新建 \(\mathcal O(\log n)\) 个节点,时空复杂度均 \(n\log n\)。
标记永久化
如果主席树需要支持区间操作,那么递归到整区间打标记时,就不能像普通线段树那样一路 pushdown
下去了,否则复杂度会退化。这时候我们就要用到标记永久化的思想,即在查询时额外记录一下目前从根节点到当前节点路径上所有节点的标记的和,传入参数中,这样递归左右儿子时再把对应的标记累加起来即可。这样复杂度不会退化,同时这种思路应用于普通的线段树上,常数也比带下推的线段树小不少。
可持久化并查集
本质上还是可持久化数组。
建两个可持久化数组(可持久化线段树),动态维护每一版本的 \(fa\) 和 \(dep\) 数组,每次合并就调用对应的 \(fa\) 和 \(dep\) 然后按秩合并即可。
由于每次操作最多涉及 \(\mathcal O(\log n)\) 个节点,所以总复杂度 \(n\log^2n\)。
可持久化 trie
本质上和可持久化线段树相同,还是每次插入一个节点就新建一个版本,时间复杂度 \(n\log v\)。
稍微贴一个以前写的板子
可持久化平衡树
万物皆可可持久化(
wjz:什么都是可持久化数组(bushi
感觉其实套路也差不多吧,以 fhq-treap 为例,每次 split 时传入两个参数 \(a,b\),假设我们要把节点 \(k\) 存在 \(a\) 一侧,那么传统的 fhq-treap 写法是直接将 \(a\) 赋为 \(k\),但是对于可持久化 fhq-treap 而言,正确写法是令 \(a\) 为一个新建的节点,然后将 \(k\) 的信息传给 \(a\) 以后继续递归,merge 操作也是同样道理。
当然一般 treap 都是带修改的,这也就牵扯到标记的下方问题,标记永久化的套路当然也是适用的,一种不用标记永久化的思路是每次 pushdown 时都重新给左右儿子建一个备份,然后把标记传到这个备份上,并把左右儿子改成这个备份并清除当前节点的标记,正确性显然,只是常数稍微大些。
得出结论:可持久化系列都可以通过在板子基础上稍微改改得到(
总结
其实理论上来说所有非均摊数据结构都可以通过每次修改都新建一个节点的方式可持久化,然后节点个数上界就是不可持久化的版本总共遍历的节点个数,这一点都可以在板子的基础上,将“修改”改为“新建”得到。
可持久化并查集的维护方式稍微与众不同一些,不过既然并查集的本质也是数组,可持久化并查集也就可以通过类似于数组的方式维护。