线段树总结
学完树状数组,很难控制住自己不学线段树,线段树是一种可以在$O(logN)$ 时间复杂度内实现区间和单点操作的数据结构:
- 单点修改/查询
- 区间修改/查询
与树状数组的区别在于:树状数组只能执行 单点修改+区间查询 或 区间修改+单点查询 。线段树能够均能支持,但是复杂度的常数系数显著大于树状数组(ps:能用树状数组就用树状数组)
线段树从何而来?我的简单理解
- 对于区间修改和区间查询,最朴素的思想就是遍历区间元素逐个修改,然而朴素的复杂度是我们无法接受的,所以我们如何改进?自然而然想到通过空间换时间的想法,通过提前存储要修改查询的“区间”,降低修改和查询的复杂度。由此我们确定了要“存区间”
- 然而如何 “存区间”?每次查询和修改的区间左端点和右端点都是任意的,不可能把所有可能的左右端点组成形成的区间存下来,根据区间之间的重叠性质,我们能够想到:既然无法存储所有的区间,不如存储一定数量不重叠的区间,只要这些区间能够拼接出所有的目标区间即可,问题就变成了怎么拆分区间?
- 如何设计存储的现成区间?每次寻找给定一个区间,寻找已经存储的现成区间进行拼接,我们的目标自然而然是 拆分的次数越少越少(最好是我直接存了这个区间),这个拆分次数最少,自然而然想到,对 最大区间进行二分拆分,以树的形式进行存储,这样拆分次数最坏就是存储树的高度(
这一步逻辑还是有点问题) - 最终我们得到了线段树这一个存储结构
线段树定义
如下图所示,线段树每个结点对应一个区间,每个长度不为1的区间的结点包括两个子节点(划分为两个子区间),叶子节点为长度为1的区间。
- 每个结点维护区间信息,例如区间和、最值等
- 通过递归修改查询等可以快速实现区间操作
- 线段树每次分裂两个子节点区间长度缩小两倍,所以线段树理想情况下为完全二叉树,普通情况下为近似完全二叉树,所以即可以使用树的方式存储,也可使用完全二叉树数组形式村存储
建树
建树代码如下:
时间复杂度和线段树结点个数,空间复杂度为栈深度
空间复杂度: $log_2n$
- 时间复杂度: $2^{log_2{n} + 1} - 1 \approx 4 n$ (若使用二叉树数组存储,空间直接开4 * n)
1 | private void buildTree(int curPos, int left, int right, int[] aimArray){ |
更新
更新代码如下:
- 若每次更新修改所有包含当前结点的区间,时间复杂度将远超目标复杂度,在实现过程中引入了”懒标记”
- 当修改某个结点时,不递归修改当前结点的孩子节点,而是将当前结点的”懒标记”,在下次修改或者查询涉及到当前节点,再进行“修改”(这里的修改时特殊的修改,即修改孩子结点,并将懒标记下推到孩子结点)- pushdown
- 通过“懒标记”确保了,更新的时间复杂度为O(logN)
1 | private void pushDown(int heapPos, int left, int right){ |
查询
查询代码如下:
- 会写更新就会写查询,而且查询比更新还要简单
- 时间复杂度为O(logN)
1 | private int query(int heapPos, int left, int right, int leftRange, int rightRange){ |
线段树例题
套模板
- 洛谷 P2357 守墓人 状态为和
- 洛谷 P3870 [TJOI2009] 开关 状态为值为1的个数
- 洛谷 P3373 【模板】线段树 2 两种区间修改操作
- 难点在于 lazylabel的设计
特殊区间状态
lc 218. 天际线问题 每个区间代表线段(不再是离散的点)
扫描线+线段树,这里每个区间不再是每个点的集合,而是真正的线段
建树修改,左区间的右端点与右区间的左端点相同(线段一分为二,不再是点一分为二)
1
2
3//区间分裂,左节点+右节点
buildTree(curPos * 2, left, mid, aimArray);
buildTree(curPos * 2 + 1, mid, right, aimArray);更新/查询修改,去掉了等号(线段相交,端点重合不叫线段相交)
1
2
3
4
5
6
7if(mid > leftRange){
update(heapPos * 2, left, mid, leftRange, rightRange, value);
}
//右节点区间交叉
if(mid < rightRange){
update(heapPos * 2 + 1, mid, right, leftRange, rightRange, value);
}
好玩的一点,这个题目不需要pushdown(因为查询只查根节点不需要pushdown,且lazylabel记录覆盖次数无法pushdown)
P2003 [CRCI2007-2008] PLATFORME 平板
- 与上一道题类似,更简单,都是存储线段