线段树是一种高端的数据结构,可以用来在区间上进行信息统计。它能够在 $O(logN)$ 的时间复杂度内实现单点/区间修改、区间找最大值/最小值/总和/…,适用于大规模的区间统计。
如下图就是一棵线段树。在结点中,你可以存对应区间的最大值,最小值,总和等等。

对于每一个结点 $i$,它的两个子结点分别是 $2i$ 和 $2i+1$。因此,在开树的数组时,最好要开到 $4N$ 的大小。
关于 $4N$,详见 OI-Wiki。
建树
下面是一个求区间和的线段树的建树代码。
通过 DFS 建树,到叶结点,然后一路回溯求出和。
| |
这里求 leftSon,rightSon,mid 的模式在线段树的所有操作中都会用到。
区间查询

对于一个不恰好的区间,我们可以不断地把它拆分成两个恰好的区间再进行合并。
如图所示,拆分过程如下:
- [3, 7]
- [3, 4], [5, 7]
- [3, 4], [5, 6], [7, 7]
在实际 DFS 过程中,我们可以分为三种情况:
- 当前结点对应区间和要查询的区间完全无关,直接退出
- 当前结点对应区间完全处于要查询的区间范围,返回当前结点的值
- 两个区间部分相交,继续拆分为 1 或 2 情况
| |
区间修改
单点修改并没有什么意思,就不讲了。
区间修改当然不是重复做单点修改,否则使用线段树就很没有必要了。为了避免走到底下去,我们要使用一个懒惰标记(lazy tag)。当一个大区间内所有的小单位都要进行同样的修改操作时,只需要在大区间做一次标记就可以了。到了必须要走下去(即查询更小的区间或修改更小的区间)的时候,再把懒惰标记下放。
下面以一个区间增加一个值并进行区间查询为例。
首先定义一个结构体,其中 add 就是记录这个区间需要增加的值。
| |
下放操作:
| |
区间增加一个值:
| |
区间查询:
| |
通过上面的代码,我们可以看到:懒惰标记就是为了不往下走,尽量在更大的区间做一次操作。必要时才往下下放标记。它不会主动做事,而是到你需要的时候才花时间去做。通过这样节省了很多时间。
小结
总的来说,线段树不是一个很难的数据结构,但是很实用。
感谢 lgj 老师!