动态开点线段树
常规写法的线段树只能维护不算很长的数组,由于空间不够,对于 $10^9$ 级别的数组却不能很好地维护。所以,我们要用到动态开点线段树。
核心思想:节点只有在有需要的时候才被创建。
比如说,要求在一个长度为 $n < 10^9$ 的数组上实现区间求和、单点修改的操作,初始数组元素值均为 0。
那么,我们一开始只创建一个根结点,接下来遵循动态开点的核心思想进行操作。
比如下面这张图的例子,我们依次修改 1, 2, 8 三个结点,途中创建了必要的结点。而在图中没有显示的空结点并没有被创建,视为 0,这样就节省了空间。

那么对于区间修改时,会有 pushdown() 操作,可能会修改一个不存在的结点。这时有两个解决方案:
- 在
pushdown()时,如果缺少孩子,就直接创建一个新的孩子就可以了。 - 使用 标记永久化 技巧(李超线段树),让结点不再进行
pushdown(),进一步节省了空间。
复杂度分析:单次操作的时间复杂度是不变的,为 $O(\log n)$。对于空间复杂度,由于每次操作都有可能创建并访问全新的一系列结点,因此 $m$ 次操作的空间复杂度是 $O(m\log n)$,不再是原本线段树的 $O(n)$。
代码实现:
| |
可持久化线段树
可持久化线段树,就是可以存储历史信息的线段树。
比如我们对数组进行了 n 次修改,然后突然希望回到第 i 个版本,然后又基于这个版本进行一些新的修改等,就是可持久化线段树需要解决的问题。
最暴力的想法是,对于每一次修改,都重新建一棵完整的线段树。然后,空间爆炸了。

我们分析一下,就会发现每一次修改时,改变的结点的个数都是线段树的深度 $\log n$。如图,我们修改了结点 $8$ 的值,受到影响的结点都在从根结点 $1$ 到 $8$ 的路径上,即 $1, 2, 4, 8$。其他的结点都是不变的。因此,我们只需要重新创建有改动的结点即可,例如 $1(new), 2(new), 4(new), 8(new)$(新的结点是动态开点的,编号和原来的不一样,因此这里用 $(new)$ 标注)。然后,例如对于新的根结点 $1(new)$,将左儿子指向新的 $2(new)$,右儿子和旧的根结点 $1(old)$ 的右儿子相同,是 $3(old)$,不用修改。
例题
Luogu-P3919 【模板】可持久化线段树 1(可持久化数组)
如题,你需要维护这样的一个长度为 $ N $ 的数组,支持如下几种操作
- 在某个历史版本上修改某一个位置上的值
- 访问某个历史版本上的某一位置的值
此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
$ 1 \leq N, M \leq {10}^6, 1 \leq {loc}_i \leq N, 0 \leq v_i < i, -{10}^9 \leq a_i, {value}_i \leq {10}^9$
程序实现:
为了保险,线段树数组的大小应设为 n << 5。洛谷上关于此的讨论。
| |
权值线段树
普通的线段树维护的是区间内最值或总和,而权值线段树每个结点维护的是在一个区间内的数出现的次数。因此,可以把权值线段树看作一个桶。举个例子,如果 $tree[1]$ 对应的区间是 $[1, 8]$,那么它的值就是 $1, 2, \cdots, 7, 8$ 在原数组中出现的次数之和。
权值线段树中,采用元素到下标数组映射的方式进行插入。当数据离散程度较大的情况,空间的利用效率比较低,这时候可以搭配离散化技巧(下面有例子)。
一个常见的应用是把权值线段树用于处理区间第 $K$ 小/大问题。
| |
注意:离散化时用 unique() 最后面一定要 -1!!!
可持久化权值线段树(主席树)
主席树名称来源于其发明人黄嘉泰的姓名的首字母缩写 HJT。
例题
如题,给定 $n$ 个整数构成的序列 $a$,将对于指定的闭区间 $[l, r]$ 查询其区间内的第 $k$ 小值。
$1 \leq n,m \leq 2\times 10^5$,$|a_i| \leq 10^9$,$1 \leq l \leq r \leq n$,$1 \leq k \leq r - l + 1$。
首先看到第 $k$ 小问题,想到要使用权值线段树。
对于每一个数,我们都
我们先把问题简化一下:每次求 $[1, r]$ 区间内的 $k$ 小值。
这时,只要找到插入 $r$ 的版本的权值线段树,然后查找即可。
回到原问题:求 $[l, r]$ 区间内的 $k$ 小值。
我们发现,主席树其实具有前缀和的性质,查询 $[l, r]$ 可以转化为 $[1, r]$ 减去 $[1, l-1]$ 的对应区间的大小。
注意数据范围,$2\times 10^5$ 个数,$|a_i| \leq 10^9$,因此用离散化。
| |