[学习笔记]线段树骚操作选讲

引入

  • 众所周知,线段树可以维护序列,进行区间操作
  • 单点加 + 区间求和
  • 区间加 + 区间求和
  • 区间加 + 区间乘 + 区间求和
  • (省略 + +\infty 行)
  • 但有些操作不能像上面三个问题一样通过简单的打标记 + 提取区间解决
  • 而需要用到一些 trick

一、势能线段树

  • 我们知道,线段树能够通过打标记实现区间修改的条件有两个:
  • (1)能够快速处理标记对区间询问结果的影响
  • (2)能够快速实现标记的合并
  • 但有的区间修改不满足上面两个条件
  • 但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限
  • 可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限
  • 区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点 return 掉
  • 可以证明复杂度为 O ( n log n × ) O(n\log n\times修改次数上限)
  • 用几个简单的栗子说明一下

一、区间开平方,区间求和

  • 一个数 x x 被开平方 O ( log log x ) O(\log\log x) 后会变成 1 1 0 0 ,继续开根后不变
  • 所以线段树节点上用一个变量记录是否区间内全为 1 1 0 0
  • 修改时递归到叶子,如果到达了区间内全为 1 1 0 0 的节点则 return 掉
  • 复杂度 O ( n log n log log x ) O(n\log n\log\log x)

二、区间取模,区间求和

  • 一个数 x x p p 取模,如果 x p x\ge p x x 至少变小一半
  • 每个节点维护区间和以及区间最小值
  • 区间对 p p 取模时仍然递归到叶子
  • 如果某节点的区间最小值小于 p p 则 return 掉
  • 复杂度 O ( n log n log x ) O(n\log n\log x)

三、区间除(下取整),区间加,区间求和

  • 区间整除 1 1 是无效的,直接跳过
  • 否则整除一个数会使区间内最大值与最小值的差至少减小一半
  • 维护区间最小值和最大值以及区间和,区间加标记
  • 整除时递归到叶子
  • 如果最小值和最大值的差为 0 0 ,则该区间内所有数都相等
  • 直接打上标记,注意这时候标记可以处理对区间和的影响
  • 复杂度 O ( n log n log x ) O(n\log n\log x)

二、李超树 / 李超线段树 / 超哥线段树

  • 考虑经典问题
  • 维护一个二维平面
  • 支持横坐标 [ l , r ] [l,r] 范围内插入一条线段,查询某个横坐标上的最高点
  • 换成人话,区间对一个等差数列取 max \max ,单点查值
  • 线段树每个节点维护一个标记(一条线段)
  • 仍然把 [ l , r ] [l,r] 拆成线段树上不超过 O ( log n ) O(\log n) 个区间
  • 我们要处理的关键问题是标记的合并,也就是两条线段 l 1 l_1 l 2 l_2 放在一起的情况
  • 如果在当前节点对应区间 [ l , r ] [l,r] l 1 l_1 完全在 l 2 l_2 之上,则该区间打上标记线段 l 1 l_1
    在这里插入图片描述
  • 如果 l 2 l_2 完全在 l 1 l_1 之上同理
    在这里插入图片描述
  • 最重要的情况: l 1 l_1 l 2 l_2 在横坐标 m i d = l + r 2 mid=\lfloor\frac{l+r}2\rfloor 的左边, l l 的右边位置相交,在 [ l , r ] [l,r] 的右半段 l 2 l_2 l 1 l_1 之上
    在这里插入图片描述
  • 这时候将 l 2 l_2 保留在当前节点上, l 1 l_1 的前半段下传到左子节点
  • 注意这时候 l 1 l_1 的前半段可能会和左子节点缓存的线段相交
  • 所以这时候需要往左子节点递归
  • 还有 3 3 种情况和上面差不多
  • 往交点所在的子节点递归,另一半区间内在上方的线段保留,在下方的线段下传
  • 复杂度 O ( n log 2 n ) O(n\log^2n)

三、线段树维护单调子序列

  • 考虑经典问题
  • 给定一个序列,支持单点修改
  • 询问给出 [ l , r ] [l,r]
  • 求有多少个 i [ l , r ] i\in[l,r] 满足 i = l i=l 或者 [ l , i 1 ] [l,i-1] 内任何一个数都不大于第 i i 个数
  • 换句话说,求 [ l , r ] [l,r] 内有多少个位置 i i [ l , r ] [l,r] 内对应的以 i i 为结尾的前缀最大值
  • 定义函数 q u e r y ( u , x ) query(u,x)
  • 表示线段树 u u 节点对应区间内,有多少个数不小于 x x 且是对应区间的前缀最大值
  • 线段树上维护区间最大值
  • 如果 u u 对应的区间最大值小于 x x q u e r y ( u , x ) = 0 query(u,x)=0
  • u u 的左右子树分别为 l c lc r c rc
  • 如果 l c lc 的最大值小于 x x q u e r y ( u , x ) = q u e r y ( r c , x ) query(u,x)=query(rc,x)
  • 否则 q u e r y ( u , x ) = q u e r y ( l c , x ) + q u e r y ( r c , max l c ) query(u,x)=query(lc,x)+query(rc,\max_{lc})
  • 其中 max l c \max_{lc} 为节点 l c lc 对应区间最大值
  • 注意到 q u e r y ( r c , max l c ) query(rc,\max_{lc}) 仅和 u u 有关
  • 所以在节点 u u 上存储一个变量 r i u = q u e r y ( r c , max l c ) ri_u=query(rc,\max_{lc})
  • 这样就能通过 O ( n log n ) O(n\log n) 的建树之后 O ( log n ) O(\log n) 查询了
  • 修改直接重新计算 max \max r i ri O ( log 2 n ) O(\log^2n)
  • 询问时把 [ l , r ] [l,r] 拆成线段树上的 m m (不超过 O ( log n ) O(\log n) )个区间 [ l 1 , r 1 ] [l_1,r_1] [ l 2 , r 2 ] [l_2,r_2] ,…, [ l m , r m ] [l_m,r_m]
  • 设对应的节点分别为 u 1 , u 2 , , u m u_1,u_2,\dots,u_m
  • 并另设 p r e pre ,初始为 -\infty
  • i i 1 1 m m ,重复 m m 次下面的操作
  • (1)将 q u e r y ( u i , p r e ) query(u_i,pre) 加进询问结果
  • (2)如果 q u e r y ( u i , p r e ) 0 query(u_i,pre)\ne0 p r e max u i pre\leftarrow\max_{u_i}
  • 复杂度 O ( log 2 n ) O(\log^2n)
  • 总复杂度 O ( n log 2 n ) O(n\log^2n)

四、题目

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/84398038