势能线段树
也叫吉司机线段树。
用途
区间最值操作
给你一个长度为 n n n 的序列,有 Q Q Q 次操作,操作类型如下:
0 u v t
:对于所有
i
i
i , $u \leq i \leq v $ ,
min
(
a
i
,
t
)
→
a
i
\min(a_i,t) \to a_i
min(ai,t)→ai ;
1 u v
:对于所有
i
i
i , $u \leq i \leq v $ , 输出最大的
a
i
a_i
ai ;
2 u v
:对于所有
i
i
i , $u \leq i \leq v $ , 输出的
a
i
a_i
ai 的和。
对于 1
和 2
,在学普通的线段树时就应该掌握,而他们也确实不是今天的主角。
看到 0
,怎么维护?
这就要用到势能线段树了。
我们在线段树中维护区间的最大值,最大值个数, 严格次大值(一定小于最大值),当然,题目还要求总和,所以这个也要加上。
那我们如何更新呢?
当 0
出现时,我们进入线段树,会出现三种情况:
- 当一个区间的最大值小于等于 t t t 时,直接退出,因为该操作肯定不会对它造成影响;
- 当一个区间的最大值大于 t t t ,而严格次大值小于 t t t 时,我们只要拿最大值出来更新就可以了;
- 当一个区间的严格次大值大于等于 t t t 时,我们让它暴力扫下去,也就是让它的子区间去更新。
有人会疑惑了:“这怎么保证时间复杂度?”
确实,这是一个令人困惑的问题,但是可以仔细想一下:
我们将一个区间内不同数字的种类数叫做 势能 ,那么一整棵线段树上的势能总和最大为 n log 2 n n\log_2{n} nlog2n ,而我们每做一次暴力修改,整体势能会至少减一,那么暴力执行最多 n log 2 n n\log_2{n} nlog2n 次,时间复杂度也就是 O ( n log 2 n ) O(n\log_2{n}) O(nlog2n)。
这也是为什么它叫做势能线段树。
不过,这是不带其他种类的修改的情况,带上修改后时间复杂度就变成了 O ( n log 2 2 n ) O(n\log_2^2{n}) O(nlog22n)。
这只是势能线段树的其中一种用途,叫做区间最值操作,除了最小值还有最大值,不过都是一样的。
区间历史最值
给你一个长度为 n n n 的序列,有 Q Q Q 次操作,操作类型如下:
0 u v t
:对于所有
i
i
i , $u \leq i \leq v $ ,
a
i
a_i
ai 加上
t
t
t ;
1 u v
:对于所有
i
i
i , $u \leq i \leq v $ , 输出最大的
a
i
a_i
ai ;
2 u v
:对于所有
i
i
i , $u \leq i \leq v $ , 输出最大的
a
i
a_i
ai 达到过的值;
那么我们又该怎么维护这个东东呢?
我们在线段树中维护区间的最大值 m x mx mx,历史最大值 h m x hmx hmx,前缀加和 s s s ,从上一次 pushdown 操作后最大的前缀加和 m s ms ms。
然后在操作时,最重要的是 pushup 和 pushdown ,
对于这三个简单操作,pushup 并不难,pushdown 才是重点。
在 pushdown 的时候,我们要做一个
tr[son].hmx=max(tr[son].hmx,tr[son].mx+tr[pa].ms)
和 tr[son].ms=max(tr[son].ms,tr[son].s+tr[pa].sm)
。
就相当于把父节点上的操作接到了子节点上,我们可以用两个队列相接来理解。
这样就可以保证区间历史最值的更新了。
区间历史最值还可以与区间赋值等一坨东西结合起来,总之很烦。
区间第 K K K 小值
这个属实是我没想到的…
给你一个长度为 n n n 的序列,有 Q Q Q 次操作,操作类型如下:
0 u v t
:对于所有
i
i
i , $u \leq i \leq v $ ,
min
(
a
i
,
t
)
→
a
i
\min(a_i,t) \to a_i
min(ai,t)→ai ;
1 u v k
:对于所有
i
i
i , $u \leq i \leq v $ , 输出第
k
k
k 小的
a
i
a_i
ai ;
基础的区间第 K K K 小值查询就是在权值线段树上二分,找到那个值,这里也是一样。
在修改时,外层的线段树可以打上取 min \min min 的标记,但这里有一个问题:它无法实现快速的标记下传与修改。
这个时候,我们可以把相同的数放到一起修改,这就体现了势能线段树的思想。
修改时,对于外层线段树上拆出的 log 2 n \log_2^n log2n 个节点,找出这个节点上所有本质不同的大于 t t t 的数,在这个节点及其祖先上把这些数改为 t t t 即可。
我们发现把每个节点内本质不同的数的个数减一,都要在 log 2 n \log_2{n} log2n 棵内层线段树上修改,时间复杂度直接到了 O ( log 2 2 n ) O(\log_2^2{n}) O(log22n) 。
总复杂度为 $ O((n+Q)\log_2^3{n})$ 。
详见 [学习笔记]吉司机线段树 - epic01 - 博客园 (cnblogs.com).
关于这个用途,我找不到题源(难受)。
模版题
区间最值操作
- Gorgeous Sequence - HDU 5306 - Virtual Judge (vjudge.net):区间赋最小值,区间和。
- 最假女选手 - 黑暗爆炸 4695 - Virtual Judge (vjudge.net):区间赋最小,大值,区间加,区间和,区间求最大,小值。
- U180387 CTSN loves segment tree - 洛谷 | 计算机科学教育新生态 (luogu.com.cn):双数组结合,区间赋最小值,区间加。
- Mzl loves segment tree:区间赋最小,大值,区间加。
(3,4题详见区间最值操作 & 区间历史最值 - OI Wiki (oi-wiki.org))
区间历史最值
- CPU 监控 - 洛谷 P4314 - Virtual Judge (vjudge.net):区间加,区间覆盖,历史最大值。
- Good Subsegments - CodeForces 997E - Virtual Judge (vjudge.net):历史区间最小值,历史区间最小值个数
混合题目
相关资料
- 线段树历史最值问题 - Flandre-Zhu - 博客园 (cnblogs.com)
- [学习笔记]吉司机线段树 - epic01 - 博客园 (cnblogs.com)
- 区间最值操作 & 区间历史最值 - OI Wiki (oi-wiki.org)