算法与数据结构 4 - 主席树(可持久化权值线段树)
0. 主席树的名字
主席树和主席没有关系。
它之所以叫主席树,是因为它的发明者叫 HJT ,和时任这一职务的那个人一样。
1. 例题
静态查询数组 \(a\) 中 \([l, r]\) 第 \(k\) 大的值。
一种暴力的方法是:每次询问对区间进行排序。这样的时间复杂度是 \(O(mn \text{log}n)\),妥妥地超时。
2. 二分的应用
先来考虑:假如已经知道值域 \(W=[1, m]\) 内每个数在 \(a[l... r]\) 的出现次数,那么可以判断 \(x\in[1, \frac{1+m}{2}]\) 中 \(x\) 在 \(a[l... r]\) 里出现了多少次。
如果出现次数 \(>k\),则说明区间第 \(k\) 大的数 \(p\in[1, \frac{1+m}{2}]\),然后就可以继续二分;如果出现次数 \(<k\),则说明区间第 \(k\) 大的数 \(p\in[\frac{1+m}{2}+1, m]\),然后也可以继续二分。
然后,为了求出 \(a[l...r]\) 中数值在 \([p, q]\) 的数有多少个,需要引入权值线段树和可持久化数据结构。
3. 权值线段树和可持久化数据结构
在权值线段树中,若节点 \(s\) 维护区间 \([l,r]\),则它的值表示在整个原数组中,数值在 \([l,r]\) 中数字的个数。这样,就实现了求数值在 \([p, q]\) 的数有多少个。
对于限定的 \(a[l...r]\),可以按顺序将 \(a_i\) 插入到权值线段树里。每插入一个数字 \(a_i\) 记录一下这棵线段树 \(\text{sgt}_i\)。接下来,用 \(\text{sgt}_r - \text{sgt}_{l-1}\) 即可得到区间 \([l, r]\) 的权值线段树。
为了解决空间不够的问题,需要对插入操作进行一些改进。可以发现线段树的单点修改最多改变 \(\text{log}_m\) 个节点的值。那么可以在每次修改时新开一个节点,而不是修改原来的节点。接下来,将新节点的左右儿子指针指向原节点的左右儿子;如果儿子也需要修改,则新开一个儿子节点,然后继续上述操作。这样,只需要找到每个历史版本的根节点,就可以遍历这个版本的线段树。
4. 总结
主席树是一种可持久化数据结构,通过查询两个历史版本实现了类似于前缀和的效果,实现了求出 \(a[l...r]\) 的权值线段树,进而得到区间第 \(k\) 大的值。
本文来自博客园,作者:cwkapn,转载请注明原文链接:https://www.cnblogs.com/cwkapn/p/18666927


浙公网安备 33010602011771号