《区间最值操作与历史最值问题》阅读心得

本文内容主要分为三个部分:介绍竞赛中常见的懒标记线段树,支持区间最值操作的线段树,支持查询区间历史信息的线段树。

区间最值操作线段树

例题 \(1\)

普通的懒标记线段树无法支持在区间取 \(\min\) 的同时快速更新区间和。于是本文基于势能分析法,提出了一种看似暴力,实际上复杂度上界低,且运行效率较高的做法。

我们在线段树每个节点上维护区间和,区间最大值的同时,同时维护区间严格次大值,区间最大值的个数,以及标记,下文分别记为 \(sum,mx,se,cnt,tg\)

我们对于后两种操作,均和普通线段树一样,终点关注第一个操作,若将区间 \([l,r]\)\(v\) 取 min,分为以下击中情况讨论:

  • \(mx\le v\):这种情况下取 min 对区间不会产生任何影响,返回即可。

  • \(se<v<mx\):这种情况下只会改变区间最大值,思考最大值变化带来的影响,即 \(sum'\gets sum-(mx-v)cnt,mx'\gets v\)。并打上标记。

  • \(v\le se\):这种情况下仅依靠当前节点存储的信息不足以更新,将标记下传后向左右两边递归暴力计算。

这种做法看似时间复杂度 \(O(mn)\),但有这严格上界 \(O(n\log n)\)。容易发现,若暴力递归计算,则这个节点所对应的区间内元素种类数一定减小(最大次大值都小于 \(v\))。而一层至多有 \(n\) 种元素,所以暴力递归的复杂度即为 \(O(m\log n)\),其他操作的复杂度也均为 \(O(m\log n)\),综上时间复杂度为 \(O(m\log n)\),且运行效率较快,可以通过 \(n,m\le 10^6\) 的数据。

例题 \(2\)

本题相较与上一题仅仅多出了区间加操作,但上一题中基于元素种类数的分析就彻底不在适用。不过我们沿用上一题的做法,记录区间最大,次大,最大数个数,和,加法标记 \(ad\) 与取 \(\min\) 的标记 \(tg\);表示先将区间内的数加上 \(ad\),再对 \(tg\)\(\min\) 的信息。若打上取 \(\min\) 标记,则与上一题相同,若打上加法标记,则对最大,次大,和修改的同时,令 \(tg\gets tg+ad\)

虽然先前的分析已经不在适用,但程序的时间复杂度仍有 \(O(m\log^2 n)\) 的上界,且运行速度较快,趋近与 \(O(m\log n)\)。在打上加法标记的时候,需要特殊处理区间次大,\(tg\) 标记不存在的情况。

通过本题,还引申出一种解决区间最值问题的重要方法。不难发现,加法标记的作用对象是区间内的所有数字,而取 \(min\) 标记的对象仅仅是区间最大值。所以我们可以将“对最大值取 \(\min\) 的标记”转化为“对最大值加/减的标记”,并在标记下传时仅将这种标记下传给区间内包含最大值的儿子,这样,就将取 \(\min\) 操作转化为了区间加减操作,在维护复杂信息时可以有效减少代码量。

例题 \(3\)

注意到暴力递归过程中元素种类数不断减小,继续沿用例题 \(1\) 的做法,对于区间最大值维护一套标记,对于区间最小值维护一套标记,基于例题二分析的时间复杂度仍为 \(O(m\log^2 n)\),实际运行效率接近 \(O(m\log n)\)

注意两套标记可能重合,即对区间次大值和最小值相同,则对区间取 \(\max\) 也要计算对区间次大值的影响。

例题 \(4\)

对于操作 \(3\),若 \(x\ne 0\),则 \(B_i\) 一定会变化。

对于操作 \(1,2\) 还是维护两套标记,每次若打上区间取 \(\max/\min\) 的标记,则说明最小/大值一定改变,分别用两个标记维护这一部分的变化量,下传标记时将这两标记给包含最小/大值的儿子。同时记录区间的 \(B_i\) 之和即可,时间复杂度 \(O(m\log^2 n)\)

例题 \(5\)

还是维护一套 \(\max,\min\) 标记,但对于打上了 \(\max,\min\) 标记后,区间的最大 \(A_i+B_i\) 值难以更新。我们将所有 \(A_i+B_i\) 值分为 \(4\) 类,用 \(C_{1/0,1/0}\) 表示区间内 \(A_i\) 是/不是最大值,\(B_i\) 是/不是最大值的最大 \(A_i+B_i\),这样我们对 \(A/B\) 区间的最大值更新时,可以找个每个更新的变化量对 \(C\) 上各个位置的影响,时间复杂度 \(O(m\log^2 n)\)

设计 pushup 函数时,如果大力分类讨论,会非常复杂。但实际上可以通过技巧简单维护:若 \(A\) 子区间内最大值和当前区间内最大值相同,则子区间的 \(C_{1,i}\) 可以贡献到当前区间 的 \(C_{1,j}\),子区间的 \(C_{0,i}\) 可以贡献到当前区间的 \(C_{0,j}\);若 \(A\) 子区间内最大值和当前区间内最大值不相同,则子区间的 \(C_{1,i},C_{0,i}\) 可以贡献到当前区间的 \(C_{0,j}\),借助简单逻辑运算即可完成:

F(i,0,1)F(j,0,1)c[i][j][p]=-inf;
f1=(ma[ls]==ma[p]),f2=(mb[ls]==mb[p]);
F(i,0,1)F(j,0,1)c[i&f1][j&f2][p]=max(c[i&f1][j&f2][p],c[i][j][ls]);
f1=(ma[rs]==ma[p]),f2=(mb[rs]==mb[p]);
F(i,0,1)F(j,0,1)c[i&f1][j&f2][p]=max(c[i&f1][j&f2][p],c[i][j][rs]);

例题 \(6\)

在没有区间最值操作的情况下,本题的一种经典做法是维护差分序列的区间 \(\gcd\) 和原序列的单点值,我们可以把这种做法扩展。

事实上,区间内数之间的顺序并不重要,依照经典思想,将非最大值的 \(\gcd\) 和最大值单独维护,非最大值的 \(\gcd\) 可以沿用上述做法,维护区间非最大值的首,尾(因为合并两个区间时需要考虑分界点之间的差分对 \(\gcd\) 的影响)。得到非最大值的 \(\gcd\) 后与最大值再取 \(\gcd\) 即可解决,时间复杂度 \(O(m\log^2 n\log V)\)

总结

对于区间最值操作,考虑将区间内的数按照最大值和非最大值维护,非最大值暴力维护,最大值可以用简单的修改维护。

区间历史最值线段树

用懒标记解决的历史信息问题

例题 \(7\)

加法标记和覆盖标记不会同时存在,若同时存在,可以将加法标记加到覆盖标记上。所以考虑如何在维护最大值,历史最大值的情况下,单独维护加法,覆盖操作的影响。

以加法为例:我们将所有打上的加法标记写成序列形式:如 \(+1,+2,-4\) 表示对当前区间依次打上的 \(+1,+2,-4\) 的加法标记,正常情况下我们仅仅需要记录这个序列的和来代表整个序列操作。但我们在维护区间最大值的同时还需要维护区间历史最大值,因此还需要记录这个操作序列的最大前缀和,即 \(+3\)。每次下传标记,同时下传加法标记和历史最大加法标记。用原来的 \(mx\) 值和历史最大标记更新现在的 \(hmax\) 值。其他的正常维护即可,覆盖操作同理,时间复杂度 \(O(m\log n)\)

例题 \(8\)

注意到题目是单点查询,所以我们只需要考虑如何将标记之间的合并和信息处理即可,查询时直接将所有的标记推到叶子节点。

我们设计一种标记 \((a,b)\),对于一个值 \(v\),打上标记表示 \(v\gets \max(v+a,b)\)。容易发现对于前三种操作打上的标记即为 \((-x,0),(x,-\infty),(-\infty,x)\)。考虑标记之间的合并,若对于一个 \(v\) 值分别按照顺序打上了 \((a,b),(c,d)\) 标记,则最后的结果是 \(v\gets \max(\max(v+a,b)+c,d)\),把 \(+c\) 分配进前一个 \(\max\)\(v\gets \max(v+a+c,\max(b+c,d))\),因此得到的新标记是 \((a+c,\max(b+c,d))\)

操作 \(5\) 为单点查询历史最值,将这些标记关于 \(v\) 的取值画成图像,则图像是由 \(y=b\) 的直线和一条 \(y=x+c\) 中二者图像上方的一段构成。若对于两个标记 \((a,b),(c,d)\) 合并,则取这两端图像的上方部分,仍然形同与普通标记的图像,则合并后得到的历史最大标记可以用 \((\max(a,c),\max(b,d))\) 表示。因此直接维护,最后下传所有标记到叶子节点即可,时间复杂度 \(O(m\log n)\)

例题 \(9\)

我不会 KDT。

区间最值查询历史信息最值问题

例题 \(10\)

相对于例题 \(9\),本题的难点并不在于区间查询历史最值,而是因为查询最值的方向和标记的方向相反(查询 \(\min\),标记为 \(\max\)),如果仍然按照以前的思路设计标记,则两个标记图像的下方部分不相同于设计的标记的图像,难以表示。

我们考虑在区间最值操作的基础上解决这个问题。将区间内的数按照最大值和非最大值分别维护,考虑在普通最值操作的基础上加入历史最值,分别对最大值和非最大值维护两套加法标记,分别是加法标记和历史最大加法标记。每次对最大值打上标记后更新区间历史最大值即可,按照例题 \(7\) 的方法照做,时间复杂度为 \(O(m\log^2 n)\)

无区间最值的区间查询历史信息和问题

文章通过构造序列 \(A\) 和历史序列 \(B\) 之间的辅助序列 \(C\),通过维护 \(C,A\) 来维护 \(B\)

区间历史最小值和

\(C_i=A_i-B_i\),对于区间加 \(x\),若 \(A_i+x\ge B_i\),则 \(C_i\gets C_i+x\),若 \(A_i+x<B_i\),则 \(C_{i}\gets 0\),容易发现区间加对 \(C\) 的操作形如 \(C_i\gets \max(C_i+x,0)\),则 \(C\) 可以用简单区间最值线段树解决,\(A\) 用普通线段树解决,则 \(\sum B_i=\sum A_i-\sum C_i\)。时间复杂度 \(O(m\log^2 n)\)

区间历史最大值和

\(C_i=A_i-B_i\),对于区间加 \(x\),若 \(A_i+x\le B_i\),则 \(C_i\gets C_i+x\),若 \(A_i+x>B_i\),则 \(C_{i}\gets 0\),容易发现区间加对 \(C\) 的操作形如 \(C_i\gets \min(C_i+x,0)\),则 \(C\) 可以用简单区间最值线段树解决,\(A\) 用普通线段树解决,则 \(\sum B_i=\sum A_i-\sum C_i\)。时间复杂度 \(O(m\log^2 n)\)

区间历史版本和

假设当前已经进行了 \(t\) 个操作,则 \(C_i=B_i-tA_i\),若区间加 \(x\),则对 \(C\) 区间加 \(tx\),用懒标记维护 \(C,A\) 即可,时间复杂度 \(O(m\log n)\)

有区间最值的区间查询历史信息和问题

例题 \(11\):过于困难。

posted @ 2025-07-24 00:02  zuoqingyuan111  阅读(20)  评论(0)    收藏  举报