引入
- 众所周知,线段树可以维护序列,进行区间操作
- 单点加 + 区间求和
- 区间加 + 区间求和
- 区间加 + 区间乘 + 区间求和
- (省略 + ∞ +\infty +∞ 行)
- 但有些操作不能像上面三个问题一样通过简单的打标记 + 提取区间解决
- 而需要用到一些 trick
一、势能线段树
- 我们知道,线段树能够通过打标记实现区间修改的条件有两个:
- (1)能够快速处理标记对区间询问结果的影响
- (2)能够快速实现标记的合并
- 但有的区间修改不满足上面两个条件
- 但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限
- 可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限
- 区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点 return 掉
- 可以证明复杂度为 O ( n log n × 修 改 次 数 上 限 ) O(n\log n\times修改次数上限) O(nlogn×修改次数上限)
- 用几个简单的栗子说明一下
一、区间开平方,区间求和
- 一个数 x x x 被开平方 O ( log log x ) O(\log\log x) O(loglogx) 后会变成 1 1 1 或 0 0 0 ,继续开根后不变
- 所以线段树节点上用一个变量记录是否区间内全为 1 1 1 或 0 0 0
- 修改时递归到叶子,如果到达了区间内全为 1 1 1 或 0 0 0 的节点则 return 掉
- 复杂度 O ( n log n log log x ) O(n\log n\log\log x) O(nlognloglogx)
二、区间取模,区间求和
- 一个数 x x x 对 p p p 取模,如果 x ≥ p x\ge p x≥p 则 x x x 至少变小一半
- 每个节点维护区间和以及区间最小值
- 区间对 p p p 取模时仍然递归到叶子
- 如果某节点的区间最小值小于 p p p 则 return 掉
- 复杂度 O ( n log n log x ) O(n\log n\log x) O(nlognlogx)
三、区间除(下取整),区间加,区间求和
- 区间整除 1 1 1 是无效的,直接跳过
- 否则整除一个数会使区间内最大值与最小值的差至少减小一半
- 维护区间最小值和最大值以及区间和,区间加标记
- 整除时递归到叶子
- 如果最小值和最大值的差为 0 0 0 ,则该区间内所有数都相等
- 直接打上标记,注意这时候标记可以处理对区间和的影响
- 复杂度 O ( n log n log x ) O(n\log n\log x) O(nlognlogx)
二、李超树 / 李超线段树 / 超哥线段树
- 考虑经典问题
- 维护一个二维平面
- 支持横坐标 [ l , r ] [l,r] [l,r] 范围内插入一条线段,查询某个横坐标上的最高点
- 换成人话,区间对一个等差数列取 max \max max ,单点查值
- 线段树每个节点维护一个标记(一条线段)
- 仍然把 [ l , r ] [l,r] [l,r] 拆成线段树上不超过 O ( log n ) O(\log n) O(logn) 个区间
- 我们要处理的关键问题是标记的合并,也就是两条线段 l 1 l_1 l1 , l