Chapter 6 - The Segment Tree
定义
注意
补充、解释
其他
1 Motivation
Given an array A[1000000], and we need to frequently calculate the sum of the numbers in an arbitrary range [L, R].
如果每次都从L算到R,时间复杂度都是
利用线段树,我们可以做到查询某个区间的和以及更新区间都是
线段树可以支持任何区间聚合操作,比如区间里找最大/最小值,我们以区间求和为例来说明。
把整个数组区间不断二分,每个节点代表一个连续区间,得到一棵树,我们可以补上一定的空间使其成为完全二叉树,一般需要
1 | 1 |
这个例子
2 Build
1 | int A[MAX]; |
例如
1 | 1 |
Build 过程会依次访问:1 → 2 → 4 → 8 → 9 → 5 → 3 → 6 → 7一共 9 次访问,每次都是 O (1) 操作。
总时间 = 9 × O (1) = O (9) = O (5) = O (N)
如果我们树开
Build只用做一次
3 Query

我们查找[2,4]的区间和,先看根结点,它是[0,4]区间和,有重叠,它的左边是[0,2],右边是[3,4],都不完全覆盖查找的区间,所以都要进入
先看左边,左边我们只需要找和[2,4]重合的[2,2],进入[0,2]后检查左右,右边刚好重合,所以返回5
再看右边,右边我们只需要找和[2,4]重合的[3,4],刚好进入[3,4]后直接刚好重合,返回11
最后相加,返回16
1 | int Query(int node, int start, int end, int left, int right) |
也就是说,通过case 3,我们总能把情况拆成case 1/2
注意程序实现中和算法有些许不同,程序实现中我们的查询区间left,right在传递中从没变过。
但在算法中,如先看左边,左边我们只需要找和[2,4]重合的[2,2],进入[0,2]后检查左右,右边刚好重合,所以返回5
好像我们进入[0,2]判断时改成了判断它和[2,2]的关系,但这个[2,2]是怎么来的呢?其实是我们发现[0,2]和[2,4]只有[2,2]是重合的,但实际程序中,我们并没有改变left和right,所以case 2是left <= start && end <= right而不是left == start && end == right,如[0,2]属于case 3,进入右边后其实是left=2,right=4,start=end=2,它仍属于left(2) <= start(2) && end(2) <= right(4),所以我们应该这么理解:
我们查找[2,4]的区间和,先看根结点,它是[0,4]区间和,与[2,4]有重叠(即不是完全不重合或完全被包住),它的左边是[0,2],右边是[3,4],都要进入
先看左边,左边[0,2]与[2,4]也只是部分重叠,左边没交集,返回0,右边[2,2]被[2,4]完全包住,所以返回5,最后返回0+5=5
再看右边,[2,4]完全包住[3,4]返回11
最后返回5+11=16
总结:
每个节点只需要回答一个问题:
在我负责的这个小区间 [start, end] 里,属于全局查询 [L, R] 的部分,总和是多少?
它不需要改查询条件,它只需要拿自己的区间 [start, end] 去和固定的 [L, R] 比:
如果我管的人一个都不在查询里 → 我贡献 0
如果我管的人全部都在查询里 → 我直接贡献我管的所有人的总和
如果一部分在一部分不在 → 我去问我的两个下属同样的问题,然后加起来
由于每次查询最多访问 2*logN 个结点,即从根走到底,而完全二叉树的树高是
4 Update
单点更新:找到对应的叶子节点,更新它的值,然后回溯向上更新所有父节点的值。
1 | void Update(int node, int start, int end, int idx, int val) |
5 Lazy Propagation
现在我们要对区间[L,R]里的所有数都加上一个常数,该怎么办?
如果是单点更新,复杂度就是
懒标记的核心思想
延迟更新:
我先不着急把更新传到每一个叶子节点,而是先在对应的大节点上记个账(打个标记)。只有当我必须去查这个大节点的子节点时,我才把之前记的账推下去(Propagate)给子节点。
1 | 根1: [0,4] 25 |
区间更新:[0, 2] 加 3
我们要给 0-2 号元素都加 3。
根节点 [0,4]:部分重叠,往左右儿子走
- 节点 2 [0,2]:✅ 完全包含!
先更新节点 2 的值:14 + 3*3 = 23(区间长度 3,每个加 3,总和加 9)
给节点 2 打个懒标记:lazy[2] = 3
直接返回!不往下走了! - 节点 3 [3,4]:无重叠,不管
- 回溯更新,把1更新成34
现在查询 [0, 1] 的和
这时候我们必须去访问节点 2 的子节点了,所以必须把懒标记推下去!
PushDown(推标记)操作:
检查节点 2 有没有懒标记:有,lazy[2]=3
把标记推给左孩子 4:
更新节点 4 的值:9 + 2*3 = 15(区间长度 2)
给节点 4 打标记:lazy[4] += 3
把标记推给右孩子 5:
更新节点 5 的值:5 + 1*3 = 8(区间长度 1)
给节点 5 打标记:lazy[5] += 3
清除节点 2 的标记:lazy[2] = 0
ai的实现
1 |
|
这样,一次区间更新就做到了
- Title: Chapter 6 - The Segment Tree
- Author: Hare Fuyukawa
- Created at : 2026-04-20 16:16:30
- Updated at : 2026-06-03 17:38:49
- Link: https://redefine.ohevan.com/Data-Structure/DS/FDS-7/
- License: This work is licensed under CC BY-NC-SA 4.0.