对于现在的我来说,只要有“可持久化”这个标签,一定伴随着 “毒瘤“,毕竟我刚学会…
好的首先介绍一下主席树是啥。主席树是“可持久化线段树”(Persistant Segment Tree
)的中文民间俗称。不知道是因为有人把 Persistant
看成了 Presidant
,还是因为它的发明者是 HJT
(和某一任国家主席简称相同),被叫做“主席树”。
但是,可持久化是啥呢?
可持久化是啥
可持久化是亿点小优化,当你要开 n n n 个线段树,但是线段树之间只差一次修改的时候,把空间复杂度降到 O ( n log n ) O(n\log n) O(nlogn)。
如何优化呢?考虑我们现在要对一个单点进行修改,那么我们能影响到的位置(就是这个点以及它的祖先),一共才 log n \log n logn ,个而其它位置都一样,所以我们可以 只新建 log n \log n logn 个位置,整一个副本出来,其余位置直接继承原树即可。
蒯一张图举个例子, n = 5 n=5 n=5,修改了第四个点:
(原作者)
(原博客)
(侵权删)(私信 3348064478@qq.com)
这样就能节省下超级多的空间!
但是,有利总有弊。这样的结构破坏的原来线段树的编号的完美特性,不能再用 index<<1
和 index<<1|1
来访问左右儿子了。我们要在线段树中多维护两个元素,左儿子和右儿子的编号。
经典题型
静态/动态区间第 k k k 位:给定一个序列,每次给出 l , r , k l,r,k l,r,k,求 [ l , r ] [l,r] [l,r] 中的数排序后,第 k k k 个位置的数是谁。(当然,我们不会实际去排序,所以询问操作对原序列没有影响)
如果是动态的,那你还要支持单点修改的操作,会给定 x , y x,y x,y,要支持把第 x x x 个位置上的数改成 y y y。
(假设我们离散化过了所有 a i a_i ai,和所有询问修改后变成的 y y y)
静态的问题
板子见 洛谷 3834
对于一个查询 ( l , r , k ) (l,r,k) (l,r,k) ,(假设我们能)把 a [ l ⋯ r ] a[l\cdots r] a[l⋯r] 拿出来建一颗权值线段树 T T T,查询就很容易了:把区间分成左半部分和右半部分,设左半部分中数字的个数为 l s u m lsum lsum。如果 k ≤ l s u m k\le lsum k≤lsum,那么就在左半部分中查找,否则就在右半部分中查找第 k − l s u m k-lsum k−lsum 个。
但是我们怎么把 a [ l ⋯ r ] a[l\cdots r] a[l⋯r] 拿出来建一颗权值线段树呢?这个时空复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn) 每次,再乘一个询问次数,肯定炸。
但是我们发现,线段树满足一种微妙的“可减性”:我们考虑建 n n n 颗线段树 T T T, T i T_i Ti 表示 a [ 1 ⋯ i ] a[1\cdots i] a[1⋯i] 组成的权值线段树。然后 a [ l ⋯ r ] a[l\cdots r] a[l⋯r] 组成的权值线段树的对应位置就是 T r T_r Tr 的对应位置减去 T l − 1 T_{l-1} Tl−1的对应位置。但是把 T T T 建出来,光空间就够炸的了,是 O ( n 2 ) O(n^2) O(n2) 的
考虑用上面“可持久化”的办法来优化求 T T T 的过程: T i T_i Ti 和 T i − 1 T_{i-1} Ti−1之间,差的只是在(离散化后的) a i a_i ai 位置上加一。那么我们就让 T i T_i Ti 在 T i − 1 T_{i-1} Ti−1 的基础上,复制其中的 log \log log 条链即可。这样就可以空间复杂度 O ( n log n ) O(n\log n) O(nlogn) ,时间复杂度 O ( n log 2 n ) O(n\log^2 n) O(nlog2