算法与数据结构 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\) 大的值。

posted @ 2025-01-17 09:44  cwkapn  阅读(42)  评论(0)    收藏  举报