吉老师线段树

概述

  • 吉老师线段树,较正式的名称为 segbeats,是由吉如一发明/整理的使用线段树维护区间最值操作和区间历史问题的方法。

  • 本文绝大部分内容都是抄的论文。

区间最值操作

实现原理

  • i[l,r],ai=min/max(ai,v)

  • 我们以 chkmin 操作为例,chkmax 操作对称处理即可。

  • 考察朴素线段树的区间修改,其依赖于两点:

    1. 区间整体价值的可快速计算性。

    2. 懒标记的可合并性。

  • 显然 chkmin 操作满足第二条,但不满足第一条,朴素的区间修改方式并不能达到效果(赋值操作乍一看也不满足第一条,但事实上可以暴力重构区间整体价值,而非贡献法)。

  • 我们转而考虑进行值域分段,将 chkmin 操作转化为减操作。具体来讲,我们在线段树节点上维护最大值 mx,严格次大值 se 和最大值个数 cntmx。当然,sum 等常规信息也是要维护的。

  • 记当前我们要对 [L,R]chkmin(v)。首先我们如普通线段树的区间修改操作一般进行区间下放,过程中如果 mxv 则直接返回,否则完成下放过程。

  • 对每个到达区间,我们分类讨论:

    1. mxv。事实上这种情况在刚才就返回了。

    2. se<v<mx。显然本次修改只会影响到最大值,我们令 sum=sum(mxv)×cntmx,然后 mx=v,并打上 lz=v 的标记,其他信息不变,显然这是对的。

    3. vse 时,无法直接更新此节点的信息,进一步递归。

    4. v<mx,且 se 不存在。相当于第二种情况。

  • 接下来对这一方式做以复杂度分析。不妨先分析纯最值操作下的复杂度,即只有 chkmin 一种修改操作。

  • 首先我们对这种维护方式做以一定的转化。

  • 定义 tagx 为线段树节点 x 的最大值即 mxx。然后若 tagx=tagfax,删去 x 处的标记,注意这一过程是自底向上的。

  • 则现在标记有如下几个性质:

    1. 一条祖孙链上的标记互不相同。不妨假设 uv 的祖先,若它们的标记相同,由性质 3 的 部分(此部分的证明不依赖本条性质)可以推出删标记前 uv 路径上的标记全部相同,于是应当 v 被擦掉。

    2. 至多只有 n 个。不妨认为 x 处有标记,记其来源为 y,则 xy 的链上除 x 之外的点都不应有标记,又最后一层只有 n 个点,所以至多只有 n 个标记。

    3. ysubx,tagx>tagy。显然,否则 x 子树内的最大值不小于 tagy。于是 tagxtagy,又由性质 1,tagx>tagy

    4. ai 的值相当于 lfirt 上的第一个标记的值。显然当标记在 lfi 上时成立,从而当标记在 falfi 上时,由 lfi 上没有标记,应有 tagfalfi=taglfi=ai,归纳可证本性质成立。

    5. se 相当于除 x 外,x 子树内的 tag 最大值。显然,其实可以由性质 3 推出。

  • 于是,我们的操作可以认为是:

    1. 找到对应区间,如果 mxv 则返回,否则在对应节点打一个 tag=v

    2. 维护性质。事实上,只要回收掉子树内 v 的标记,就等价于这一目的。这一问题比较复杂,我们详细谈谈:

    • 关于性质 2 的维护:性质 2 是归纳的,或者说归纳证明的性质 2 对我们更有利。先给性质 2 加一个“根上一定有 tag”。

    • 显然当 n=1 时性质 2 成立,进一步考虑把左右子树拼起来加个 fa,显然 tagfa=taglstagfa=tagrs 至少成立一个,于是两者至少擦一个,于是归纳得证。

    • 性质 2 可能不能在子树内维护完全,但可以在子树内维护到对子树成立。而返回时的 pushup 会保证对更大子树成立。

    • 充分性:

      1. 若改完后相同,则说明 vse,于是递归,递归证明到 n=1,显然 n=1 不可能有 vse 也即不相同,得证。

      2. 打了标记说明 mx>v,那么本来的标记是 mx,现在换了,得证。

      3. ,则 vse,递归到 n=1,同性质 1,得证。

      4. 只有原本 ai>v 的会受影响,同性质 2。

      5. 和性质 2 一样是子树归纳的,可以由其他性质推出,略。

    • 必要性:维护过程中,tag<v 相关信息显然不变,从而等价于回收 v 的标记,且一定是删了或者换了更小的,否则还得维护,不满足性质 3。

  • 然后我们定义标记类:

    1. 一次 chkmin 产生的标记是同一类。

    2. 同一个标记下传产生的标记是同一类。

    3. 不满足前两个条件的标记属于不同类。

  • 定义标记类 α 的权值为这一类标记连同线段树的根对应的虚树大小,记为 ϕ(α),定义势函数 Φ(sta)=αϕ(α)。我们进行如下的势能分析:

    • 考虑区间下放和打 tag 对势函数的影响。显然,到达点只有 O(log),增加 O(log)tag,即势函数只增加 O(log)。证明在朴素线段树的区间操作处已经做过了。

    • 考虑标记下推,显然只有 ϕ(α) 被增加了 1

    • 考虑进一步递归对势函数的影响。由上面的证明,我们知道递归的所有到达点 x 都满足 tagxv,从而访问到的所有点都 tagxv(由性质 2)...这一步有问题。

    • 但至少我们可以证明,进一步递归每至多跑满一条链即花费至多 O(log) 的代价,就让势函数 1。这对应着一个 O(nlog2) 的宽松上界。

    • 事实上可以证明进一步递归中每访问一个点就让势函数 1,于是摊还复杂度为 O(nlog)

  • 综上,修改操作中势函数的总变化量为 O(nlog),初始时的势函数至多为 O(n),因为 k 个点的虚树只有至多 2k1 个点(证明见虚树),进一步递归之前的总复杂度也为 O(nlog),故总复杂度为 O(nlog),单次操作均摊 O(log)

  • 考虑推广到复杂环境,即允许大部分常规操作和最值操作。

  • 首先谈谈怎么加减。我们需要一个分配律...我不太确定该怎么说,但我们能看到,min(x,y)+z=min(x+z,y+z),所以大概是加法对最值操作有分配律。故规定总是把加法分配进去,先加后做最值操作,对已有的最值操作懒标记做修改。

  • 显然标记类不再能沿用,因为区间加减会改变它(不谈区间赋值,区间赋值在标记类上甚至弱于 chkmin)。

  • 但可以修定义:一次区间加减若碰到了某一类标记的一部分,则该类标记分裂为两类;否则该类标记不变。

  • 我们把线段树的根从虚树里扔进去。我的意思是,不再额外加进来。

  • ...蚌,证不下去,没有充分理解。就算硬凑完,也是 O(nlog3)。倒是有一个 promising 的,论文中提到的另一个方向:定义势函数为标记深度和。

  • 先放弃证明复杂度吧,背住板子就可以了。

HDU5036 Gorgeous Sequence

  • 题意:区间取 min,区间求最大值,区间求和。

  • 数据范围:T100,n,m106

  • 即最值操作板题。注意本题卡常(HDU 的机子太烂了),考虑使用 fread。另外 Vjudge 上的某些翻译不可信,值域下界应为 0 而非 1

WZOI 提高题库 239 Picks loves segment tree

  • 看网址似乎总编号是 3050?不管。

  • 题意:区间取 min,区间加,区间求和。

  • 数据范围:n,m5×105

  • 即复杂环境下的最值操作板题。这可以作为一个 Ω(log) 的例证。

AcrossTheSky loves segment tree

  • 不用找了,没地方交。不如去交下一道题。

  • 题意:区间取 min,区间取 max,区间求和。

  • 感性理解一下,这两种操作对对方的影响等价于区间加对对方的影响,因此我们还是有一个 O(log2)

  • 直接复合两者的维护即可。注意区间中只有 <4 种值时,最大/小值相关信息会重合,需要特别处理。

darkBZOJ #4695. 最假女选手

  • 如果 darkBZOJ 也炸了,那么 hydro 的 BZOJ 域里的 #4695. 也是这道题。

  • 题意:区间加,取 min/max,区间求和、最大、最小值。

  • 数据范围:n,m5×105

  • 就是只涉及最值操作的常规题目的完全体吧。细节较多,我还没写。

Mzl loves segment tree

  • 题意:区间取 min,区间取 max,区间加,区间查历史变动次数和。

  • 这个...其实不算很“历史问题”。区间加是容易的,对取 min/max,利用维护的最大/最小值个数和区间长度,加加减减容易算出有多少个数字变了。

  • 至于最值操作转化成的加减操作在下标上不连续的问题,不用管它。反正 pushdown 下去的时候都会解决,连不连续无所谓,在区间内就好了。

ChiTuShaoNian loves segment tree

  • 题意:双序列,区间取 min,区间加,求双序列复合(这里是加)最大值。

  • 关于位置分类,即同时为两个最大值/为某一个/都不为,实质目标是维护两者的和,对这四类下标分别进行修改和打标记。se 当然还是要维护,说实话这信息太多了写起来一定很恶心。

  • 事实上,我们是打了四个值作为道标,藉此判断 chkmin 操作对四类下标分别的效果,四类下标(及其数量)是区间的根本信息,四个值某种意义上只是分界线或者路牌罢了。

  • 显然可以分析到 O(2klog2),这里 k 是序列数。

Dzy loves segment tree

  • 题意:区间取 min,区间加,区间求 gcd

  • 一个很套路的办法是差分,然而区间取 min...啊懒了,等我下次翻论文的时候再说吧。

  • 这道题就很明显地指出,segbeats 在维护和下标顺序相关的问题时比较无力(这里是逐项差分),因为转化成的加减操作的作用区间不连续。

区间历史最值

实现原理

  • 不妨仍然以最小值为例。我们先讨论经典操作下的区间历史最值,然后再将它推广到最值操作上。

  • 注意到历史最值和线段树的延迟修改结构在一定程度上是矛盾的。延迟修改可能导致值的峰被延迟过去了。

  • 怎么办?引入历史最值相关的懒标记,即,历史懒标记。记录某个点的懒标记的历史最大值,在下传时将它下传,作用于对应的历史最值上。

  • 在多操作时可能较为复杂,因为历史最值对于标记的性质有较为严格的要求...具体来说,正常标记下传(显然标记下传严格强于区间修改,我们将区间修改也视作一种标记下传)只需要下传标记作用于信息和下传标记作用于标记,但对于需要维护历史最值的情况,我们需要按如下次序进行下传:

    • 下传历史标记(与旧信息结合)作用于历史信息。

    • 下传历史标记(与旧标记结合)作用于历史标记。

    • 下传标记作用于信息,新信息作用于历史信息。

    • 下传标记作用于标记,新标记作用于历史标记。

  • 合计六步。其中,历史的两步必须在前,当前的两步必须在后,但内部交换顺序无妨,理由在括号里。

  • 事实上,似乎可以略去“新信息作用于历史信息”和“新标记作用于历史标记”,因为其严格不强于历史标记下传的效果。这里列出只是为了逻辑清晰。

  • 另外,应当指出的是,这一下传在有多种标记时非常严格,多种标记的互相作用可能导致某些历史标记下传是根本不可做的。可以参看 UOJ#164. [清华集训2015]V 一题中我的假做法,较有启发性。

  • 总体而言,并不提升复杂度,仍然为 O(log)

P4314 CPU 监控

  • 题意:区间加,区间赋值,区间求最大值,区间求历史最大值。

  • 数据范围:n,m105

  • 直接做就好。注意到赋值操作比较特殊,这玩意具有类似一元的性质(啥跟他结合都是它...)。

  • 考虑钦定先加后赋值,赋值后再来加相当于直接加到赋值标记上,记录历史最大加和历史最大赋值,有序下传即可。

  • 先赋值后加...这在没有历史最值的时候肯定是可行的,用赋值标记把已有的加标记覆盖掉即可。但在考虑历史最值的时候...注意到加一段-赋值一下-加一段-赋值一下-加一段-赋值一下-...这样的操作并不好处理,因为有多个历史加(被赋值覆盖掉的,而且这里还必须先历史加后历史赋值),我觉得没法做,还是上面的那种比较好。

  • 复杂度 O(log2)?应该吧,毕竟赋值操作是可以分裂标记类的。

UOJ#164. 【清华集训2015】V

  • 题意:区间加,区间减(至多减到 0),区间赋值,单点求最值和历史最值。

  • 如果没有这个奇怪的减法限制,那么平凡,就是 CPU。但是有这个限制,这就导致我们的历史最大加标记会有各种奇怪的问题。

  • 考虑把减法看成一种类似 chkmin 的操作好了。维护最小值和次小值,分三段考虑,符合 segbeats 的不同类合并,可以 O(log2) 维护。

  • 具体地,规定标记次序如下:

    • 将标记看成一种 (mlz,plz,alz),即先 chkmin,然后加,然后赋值的东西...

    • 对于常规的 tag,alz 出现代表着前面的寄掉。有 plz 之后再来 mlz...把两者合并,若 plz>=mlz 直接 plz=mlz,否则合并。毕竟大家本质还是差不多的东西嘛。模仿 CPU,把 alz 存在时的加减操作直接扔到 alz 上。

    • 对于历史 tag 则比较复杂...显然历史 tag 不关心 chkmin 到多少,维护历史最大加标记和历史最大赋值标记就好了。

  • 但是,这一做法是错误的:考虑下传次序,我们显然应该先下传所有历史标记,再下传所有常规标记,次序都是 m,p,a。简单起见,先把 a 抛掉,单独考虑 m,p 两者,若我 mpm,则 hp 应为 p 的内容,但其显然不能简单下传,若尝试将 m(不是 hm,是 m,显然 hm 无意义)先传下去,也是不可行的,因为后面的 m 会破坏这一次序。

  • 显然,一个可行的解决思路是,在计算 hp 时考虑 m 的影响。所有 m,都至多只对 mn 起到修改为 0 的效果。若区间 se 存在,则显然 hp 只要暴力作差就好了,否则本质上是整了个 ha 出来。

  • 显然这未免有些复杂了。事实上,这需要对 m 到底有没有让 mn 小于 0(因而被卡住)分讨,如果其起到这个效果那么其是 m,否则其是 p。注意随下传的进行,其可能会改变(不再是 m)。然后在计算 hp 的时候应当对是否有 se 分讨,有 se 则为 mx+pm,否则为 p,计算 ha 时也要有类似的联立。

  • ...我想我们已经从中榨出了足够多的对历史最值标记的理解。我们不再整这个明显过于复杂的东西了,注意到 m 操作最终总是变成 pa,考虑如下转定义:m(x)=(x,0),p(x)=(x,0),a(x)=(,x),这里 (a,b) 表示先区间加 a 再区间与 b 取最大值。

  • 考虑这种标记怎么合并。显然,(a,b)+(c,d)=(a+c,max(b+c,d))。然而这东西...似乎完全没法处理成历史标记啊?

  • 显然此标记本质上是一个分段函数,第一段为 k=0 的横线,第二段为 k=1 的分段函数。分段函数合并,是容易的,取最值就好咯,即任意时刻的标记全部合并起来得到的分段函数是我们的历史标记。形式都是统一的,显然可以维护,易证结合律,这里又不求和没必要维护 se 搞 segbeats 那一套,完了,O(log)

  • 也有相对简单一些(不需要分段函数合并)的方法。显然我们在最值操作一节谈到的方法,在本质上将最值操作转化为了加减操作,于是问题退化成 CPU 监控,但需要对最小值和非最小值分别维护一套标记和历史标记(注意,这并非我们闲得,而是因为两者的加标记并不相同,被迫为之)。

51Nod 1768 Rikka with Sequences

  • 题意:对 a 支持单点修,查询 i=lrai 的历史最小值。

  • 数据范围:n,m105

  • 不太传统,对吧?考虑先扔到二维平面上,单点修改变成一个左上矩形修改,即问题现在变成了矩形加,单点求历史最小值。

  • 显然基于标记永久化(因而需要交换律)的线套线做不了这个。转而使用 KDT,O(mn)

UOJ #169. 【UR #11】元旦老人与数列

  • 题意:区间加,区间 chkmax,区间求最小值和历史最小值。

  • 显然这还是可以套 V 那道题的复合标记,但这里的问题在于两个分段函数合并后的段数可能增加——炸了,乐。只能用 segbeats 转化成区间加,然后 O(log2) 维护。

P6242 【模板】线段树 3

  • 题意:元旦老人与数列再加一个区间求和。

  • 显然这就是 segbeats 本来的功能之一,简单处理即可。O(log2)

区间历史和

区间历史版本和

  • 我们先讨论经典操作下的区间历史版本和,然后再将它推广到最值操作上。

  • 记和数组为 a,区间历史版本和为 b,设法构造一个数组 c,使得我们可以通过 a,c 快速计算 b,且 c 本身的变化量不大。

  • 不妨设 c=bt×a,这里 t 为当前时间。显然,当 a 不变时,c 不变,且 b=ta+c,容易计算。

  • 考虑 a 被修改会如何。直接算出新的 a,然后用增量法调整之,显然只要在修改中记录当前的 t,这就是可行的。

  • 具体来说,记 a 变动的时间为 t,于是 c=b(t1)a,又 a=a+xb=b+a,故 c=bta=(t1)a+c+ata=(t1)a+c(t1)a=(t1)(aa)+c,即 c=c(t1)x,这里 x 为区间变动量和(即 aa)。这一区间修改是容易的(不要维护这个复杂的 tag,若区间修为给原数组加 v,则相当于给 c 的“原数组”,即单点历史版本和加 (1t)v,这个 tag 显然非常可合并)。

  • 复杂度 O(log)。唔...显然,只要套用 V 中提出的转化,我们就可以以多支付一个 log 为代价来支持最值操作。

P3246 [HNOI2016] 序列

  • 参看双指针与扫描线-扫描线。事实上,历史版本和是扫描线处理矩形加矩形求和的经典实现方式之一。

P8868 [NOIP2022] 比赛

  • 参看双指针与扫描线-扫描线。多序列,真(粗口)恶心。

区间历史最值和

  • 即求历史最值数组的和。

  • 唔...能不能推广历史版本和的方法?

  • 仍然以历史最小值为例,记最小值数组为 a,历史最小值数组为 b,构造 ci=aibi。显然 a 不变时 c 也不变,且 b=ac,容易计算。

  • 考虑 a 变动,若 a<ba=b,c=0,否则 c=c+(aa),这是显然的。本质上,a<b,方便起见取成 ab 好了,代表着 aaba=c,即 c+(aa)c+c=0,诶?

  • 实质上就是对 c 先加 aa(就是修改量),然后和 0max。这一操作我们是熟悉的...由 V,如果其的询问是 cmax,则我们有一个分段函数合并的 O(log) 做法,但显然此时我们构造的 c 是用来求 b 的区间和的...如果问的是 b 的区间最值,反而做不了,因为 amaxcmax 不一定恰为 max(ac)

  • 故还是多支付一个 log 转化成区间加减解决,由此我们有了一个 O(log2) 的做法。历史最大值也一样,只是 chkmax 变成 chkmin 罢了(当前减去历史最大值总应该是非正的)。

  • 到这我们谈的都是简单加减。显然,对最值操作,也是可以的,只要拆成加减...诶?注意到我们先前之所以是 log2,是因为总体上发出了一个最值操作。这里我们在把对原数组的最值操作拆成加减操作之后,对 c 数组的最值操作才发出,且可能是各不相同的(转化成的加减操作的变化量取决于原本的区间最值),故会发出 O(log) 个最值操作,由此可以看出 k 层嵌套会导致 k1 次拆解和一次 log2,实际效果为 O(logk+1),不过实际上我并不理解怎么继续嵌套(呃呃,题目里就定义一个 c,然后对 c 求区间历史最值和?这是手动嵌套呀),且我不理解论文里的 2k 从何而来。

  • 不过,到这里,我们的 segbeats 科技,可以说是点完了。剩下的,主要是在实践中运用和积累经验了。撒花?

posted @   未欣  阅读(750)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示