吉老师线段树
概述
-
吉老师线段树,较正式的名称为 segbeats,是由吉如一发明/整理的使用线段树维护区间最值操作和区间历史问题的方法。
-
本文绝大部分内容都是抄的论文。
区间最值操作
实现原理
-
即
。 -
我们以
操作为例, 操作对称处理即可。 -
考察朴素线段树的区间修改,其依赖于两点:
-
区间整体价值的可快速计算性。
-
懒标记的可合并性。
-
-
显然
操作满足第二条,但不满足第一条,朴素的区间修改方式并不能达到效果(赋值操作乍一看也不满足第一条,但事实上可以暴力重构区间整体价值,而非贡献法)。 -
我们转而考虑进行值域分段,将
操作转化为减操作。具体来讲,我们在线段树节点上维护最大值 ,严格次大值 和最大值个数 。当然, 等常规信息也是要维护的。 -
记当前我们要对
做 。首先我们如普通线段树的区间修改操作一般进行区间下放,过程中如果 则直接返回,否则完成下放过程。 -
对每个到达区间,我们分类讨论:
-
。事实上这种情况在刚才就返回了。 -
。显然本次修改只会影响到最大值,我们令 ,然后 ,并打上 的标记,其他信息不变,显然这是对的。 -
当
时,无法直接更新此节点的信息,进一步递归。 -
,且 不存在。相当于第二种情况。
-
-
接下来对这一方式做以复杂度分析。不妨先分析纯最值操作下的复杂度,即只有
一种修改操作。 -
首先我们对这种维护方式做以一定的转化。
-
定义
为线段树节点 的最大值即 。然后若 ,删去 处的标记,注意这一过程是自底向上的。 -
则现在标记有如下几个性质:
-
一条祖孙链上的标记互不相同。不妨假设
为 的祖先,若它们的标记相同,由性质 3 的 部分(此部分的证明不依赖本条性质)可以推出删标记前 路径上的标记全部相同,于是应当 被擦掉。 -
至多只有
个。不妨认为 处有标记,记其来源为 ,则 的链上除 之外的点都不应有标记,又最后一层只有 个点,所以至多只有 个标记。 -
。显然,否则 子树内的最大值不小于 。于是 ,又由性质 1, 。 -
的值相当于 上的第一个标记的值。显然当标记在 上时成立,从而当标记在 上时,由 上没有标记,应有 ,归纳可证本性质成立。 -
相当于除 外, 子树内的 最大值。显然,其实可以由性质 3 推出。
-
-
于是,我们的操作可以认为是:
-
找到对应区间,如果
则返回,否则在对应节点打一个 。 -
维护性质。事实上,只要回收掉子树内
的标记,就等价于这一目的。这一问题比较复杂,我们详细谈谈:
-
关于性质 2 的维护:性质 2 是归纳的,或者说归纳证明的性质 2 对我们更有利。先给性质 2 加一个“根上一定有
”。 -
显然当
时性质 2 成立,进一步考虑把左右子树拼起来加个 ,显然 和 至少成立一个,于是两者至少擦一个,于是归纳得证。 -
性质 2 可能不能在子树内维护完全,但可以在子树内维护到对子树成立。而返回时的 pushup 会保证对更大子树成立。
-
充分性:
-
若改完后相同,则说明
,于是递归,递归证明到 ,显然 不可能有 也即不相同,得证。 -
打了标记说明
,那么本来的标记是 ,现在换了,得证。 -
若
,则 ,递归到 ,同性质 1,得证。 -
只有原本
的会受影响,同性质 2。 -
和性质 2 一样是子树归纳的,可以由其他性质推出,略。
-
-
必要性:维护过程中,
相关信息显然不变,从而等价于回收 的标记,且一定是删了或者换了更小的,否则还得维护,不满足性质 3。
-
-
然后我们定义标记类:
-
一次
产生的标记是同一类。 -
同一个标记下传产生的标记是同一类。
-
不满足前两个条件的标记属于不同类。
-
-
定义标记类
的权值为这一类标记连同线段树的根对应的虚树大小,记为 ,定义势函数 。我们进行如下的势能分析:-
考虑区间下放和打
对势函数的影响。显然,到达点只有 ,增加 个 ,即势函数只增加 。证明在朴素线段树的区间操作处已经做过了。 -
考虑标记下推,显然只有
被增加了 。 -
考虑进一步递归对势函数的影响。由上面的证明,我们知道递归的所有到达点
都满足 ,从而访问到的所有点都 (由性质 2)...这一步有问题。 -
但至少我们可以证明,进一步递归每至多跑满一条链即花费至多
的代价,就让势函数 。这对应着一个 的宽松上界。 -
事实上可以证明进一步递归中每访问一个点就让势函数
,于是摊还复杂度为 。
-
-
综上,修改操作中势函数的总变化量为
,初始时的势函数至多为 ,因为 个点的虚树只有至多 个点(证明见虚树),进一步递归之前的总复杂度也为 ,故总复杂度为 ,单次操作均摊 。 -
考虑推广到复杂环境,即允许大部分常规操作和最值操作。
-
首先谈谈怎么加减。我们需要一个分配律...我不太确定该怎么说,但我们能看到,
,所以大概是加法对最值操作有分配律。故规定总是把加法分配进去,先加后做最值操作,对已有的最值操作懒标记做修改。 -
显然标记类不再能沿用,因为区间加减会改变它(不谈区间赋值,区间赋值在标记类上甚至弱于
)。 -
但可以修定义:一次区间加减若碰到了某一类标记的一部分,则该类标记分裂为两类;否则该类标记不变。
-
我们把线段树的根从虚树里扔进去。我的意思是,不再额外加进来。
-
...蚌,证不下去,没有充分理解。就算硬凑完,也是
。倒是有一个 promising 的,论文中提到的另一个方向:定义势函数为标记深度和。 -
先放弃证明复杂度吧,背住板子就可以了。
HDU5036 Gorgeous Sequence
-
题意:区间取
,区间求最大值,区间求和。 -
数据范围:
。 -
即最值操作板题。注意本题卡常(HDU 的机子太烂了),考虑使用 fread。另外 Vjudge 上的某些翻译不可信,值域下界应为
而非 。
WZOI 提高题库 239 Picks loves segment tree
-
看网址似乎总编号是 3050?不管。
-
题意:区间取
,区间加,区间求和。 -
数据范围:
。 -
即复杂环境下的最值操作板题。这可以作为一个
的例证。
AcrossTheSky loves segment tree
-
不用找了,没地方交。不如去交下一道题。
-
题意:区间取
,区间取 ,区间求和。 -
感性理解一下,这两种操作对对方的影响等价于区间加对对方的影响,因此我们还是有一个
。 -
直接复合两者的维护即可。注意区间中只有
种值时,最大/小值相关信息会重合,需要特别处理。
darkBZOJ #4695. 最假女选手
-
如果 darkBZOJ 也炸了,那么 hydro 的 BZOJ 域里的 #4695. 也是这道题。
-
题意:区间加,取
,区间求和、最大、最小值。 -
数据范围:
。 -
就是只涉及最值操作的常规题目的完全体吧。细节较多,我还没写。
Mzl loves segment tree
-
题意:区间取
,区间取 ,区间加,区间查历史变动次数和。 -
这个...其实不算很“历史问题”。区间加是容易的,对取
,利用维护的最大/最小值个数和区间长度,加加减减容易算出有多少个数字变了。 -
至于最值操作转化成的加减操作在下标上不连续的问题,不用管它。反正 pushdown 下去的时候都会解决,连不连续无所谓,在区间内就好了。
ChiTuShaoNian loves segment tree
-
题意:双序列,区间取
,区间加,求双序列复合(这里是加)最大值。 -
关于位置分类,即同时为两个最大值/为某一个/都不为,实质目标是维护两者的和,对这四类下标分别进行修改和打标记。
当然还是要维护,说实话这信息太多了写起来一定很恶心。 -
事实上,我们是打了四个值作为道标,藉此判断
操作对四类下标分别的效果,四类下标(及其数量)是区间的根本信息,四个值某种意义上只是分界线或者路牌罢了。 -
显然可以分析到
,这里 是序列数。
Dzy loves segment tree
-
题意:区间取
,区间加,区间求 。 -
一个很套路的办法是差分,然而区间取
...啊懒了,等我下次翻论文的时候再说吧。 -
这道题就很明显地指出,segbeats 在维护和下标顺序相关的问题时比较无力(这里是逐项差分),因为转化成的加减操作的作用区间不连续。
区间历史最值
实现原理
-
不妨仍然以最小值为例。我们先讨论经典操作下的区间历史最值,然后再将它推广到最值操作上。
-
注意到历史最值和线段树的延迟修改结构在一定程度上是矛盾的。延迟修改可能导致值的峰被延迟过去了。
-
怎么办?引入历史最值相关的懒标记,即,历史懒标记。记录某个点的懒标记的历史最大值,在下传时将它下传,作用于对应的历史最值上。
-
在多操作时可能较为复杂,因为历史最值对于标记的性质有较为严格的要求...具体来说,正常标记下传(显然标记下传严格强于区间修改,我们将区间修改也视作一种标记下传)只需要下传标记作用于信息和下传标记作用于标记,但对于需要维护历史最值的情况,我们需要按如下次序进行下传:
-
下传历史标记(与旧信息结合)作用于历史信息。
-
下传历史标记(与旧标记结合)作用于历史标记。
-
下传标记作用于信息,新信息作用于历史信息。
-
下传标记作用于标记,新标记作用于历史标记。
-
-
合计六步。其中,历史的两步必须在前,当前的两步必须在后,但内部交换顺序无妨,理由在括号里。
-
事实上,似乎可以略去“新信息作用于历史信息”和“新标记作用于历史标记”,因为其严格不强于历史标记下传的效果。这里列出只是为了逻辑清晰。
-
另外,应当指出的是,这一下传在有多种标记时非常严格,多种标记的互相作用可能导致某些历史标记下传是根本不可做的。可以参看
一题中我的假做法,较有启发性。 -
总体而言,并不提升复杂度,仍然为
。
P4314 CPU 监控
-
题意:区间加,区间赋值,区间求最大值,区间求历史最大值。
-
数据范围:
。 -
直接做就好。注意到赋值操作比较特殊,这玩意具有类似一元的性质(啥跟他结合都是它...)。
-
考虑钦定先加后赋值,赋值后再来加相当于直接加到赋值标记上,记录历史最大加和历史最大赋值,有序下传即可。
-
先赋值后加...这在没有历史最值的时候肯定是可行的,用赋值标记把已有的加标记覆盖掉即可。但在考虑历史最值的时候...注意到加一段-赋值一下-加一段-赋值一下-加一段-赋值一下-...这样的操作并不好处理,因为有多个历史加(被赋值覆盖掉的,而且这里还必须先历史加后历史赋值),我觉得没法做,还是上面的那种比较好。
-
复杂度
?应该吧,毕竟赋值操作是可以分裂标记类的。
UOJ#164. 【清华集训2015】V
-
题意:区间加,区间减(至多减到
),区间赋值,单点求最值和历史最值。 -
如果没有这个奇怪的减法限制,那么平凡,就是 CPU。但是有这个限制,这就导致我们的历史最大加标记会有各种奇怪的问题。
-
考虑把减法看成一种类似
的操作好了。维护最小值和次小值,分三段考虑,符合 segbeats 的不同类合并,可以 维护。 -
具体地,规定标记次序如下:
-
将标记看成一种
,即先 ,然后加,然后赋值的东西... -
对于常规的 tag,
出现代表着前面的寄掉。有 之后再来 ...把两者合并,若 直接 ,否则合并。毕竟大家本质还是差不多的东西嘛。模仿 CPU,把 存在时的加减操作直接扔到 上。 -
对于历史 tag 则比较复杂...显然历史 tag 不关心
到多少,维护历史最大加标记和历史最大赋值标记就好了。
-
-
但是,这一做法是错误的:考虑下传次序,我们显然应该先下传所有历史标记,再下传所有常规标记,次序都是
。简单起见,先把 抛掉,单独考虑 两者,若我 ,则 应为 的内容,但其显然不能简单下传,若尝试将 (不是 ,是 ,显然 无意义)先传下去,也是不可行的,因为后面的 会破坏这一次序。 -
显然,一个可行的解决思路是,在计算
时考虑 的影响。所有 ,都至多只对 起到修改为 的效果。若区间 存在,则显然 只要暴力作差就好了,否则本质上是整了个 出来。 -
显然这未免有些复杂了。事实上,这需要对
到底有没有让 小于 (因而被卡住)分讨,如果其起到这个效果那么其是 ,否则其是 。注意随下传的进行,其可能会改变(不再是 )。然后在计算 的时候应当对是否有 分讨,有 则为 ,否则为 ,计算 时也要有类似的联立。 -
...我想我们已经从中榨出了足够多的对历史最值标记的理解。我们不再整这个明显过于复杂的东西了,注意到
操作最终总是变成 和 ,考虑如下转定义: ,这里 表示先区间加 再区间与 取最大值。 -
考虑这种标记怎么合并。显然,
。然而这东西...似乎完全没法处理成历史标记啊? -
显然此标记本质上是一个分段函数,第一段为
的横线,第二段为 的分段函数。分段函数合并,是容易的,取最值就好咯,即任意时刻的标记全部合并起来得到的分段函数是我们的历史标记。形式都是统一的,显然可以维护,易证结合律,这里又不求和没必要维护 搞 segbeats 那一套,完了, 。 -
也有相对简单一些(不需要分段函数合并)的方法。显然我们在最值操作一节谈到的方法,在本质上将最值操作转化为了加减操作,于是问题退化成 CPU 监控,但需要对最小值和非最小值分别维护一套标记和历史标记(注意,这并非我们闲得,而是因为两者的加标记并不相同,被迫为之)。
51Nod 1768 Rikka with Sequences
-
题意:对
支持单点修,查询 的历史最小值。 -
数据范围:
。 -
不太传统,对吧?考虑先扔到二维平面上,单点修改变成一个左上矩形修改,即问题现在变成了矩形加,单点求历史最小值。
-
显然基于标记永久化(因而需要交换律)的线套线做不了这个。转而使用 KDT,
。
UOJ #169. 【UR #11】元旦老人与数列
-
题意:区间加,区间
,区间求最小值和历史最小值。 -
显然这还是可以套 V 那道题的复合标记,但这里的问题在于两个分段函数合并后的段数可能增加——炸了,乐。只能用 segbeats 转化成区间加,然后
维护。
P6242 【模板】线段树 3
-
题意:元旦老人与数列再加一个区间求和。
-
显然这就是 segbeats 本来的功能之一,简单处理即可。
。
区间历史和
区间历史版本和
-
我们先讨论经典操作下的区间历史版本和,然后再将它推广到最值操作上。
-
记和数组为
,区间历史版本和为 ,设法构造一个数组 ,使得我们可以通过 快速计算 ,且 本身的变化量不大。 -
不妨设
,这里 为当前时间。显然,当 不变时, 不变,且 ,容易计算。 -
考虑
被修改会如何。直接算出新的 ,然后用增量法调整之,显然只要在修改中记录当前的 ,这就是可行的。 -
具体来说,记
变动的时间为 ,于是 ,又 , ,故 ,即 ,这里 为区间变动量和(即 )。这一区间修改是容易的(不要维护这个复杂的 tag,若区间修为给原数组加 ,则相当于给 的“原数组”,即单点历史版本和加 ,这个 tag 显然非常可合并)。 -
复杂度
。唔...显然,只要套用 V 中提出的转化,我们就可以以多支付一个 为代价来支持最值操作。
P3246 [HNOI2016] 序列
- 参看双指针与扫描线-扫描线。事实上,历史版本和是扫描线处理矩形加矩形求和的经典实现方式之一。
P8868 [NOIP2022] 比赛
- 参看双指针与扫描线-扫描线。多序列,真(粗口)恶心。
区间历史最值和
-
即求历史最值数组的和。
-
唔...能不能推广历史版本和的方法?
-
仍然以历史最小值为例,记最小值数组为
,历史最小值数组为 ,构造 。显然 不变时 也不变,且 ,容易计算。 -
考虑
变动,若 则 ,否则 ,这是显然的。本质上, ,方便起见取成 好了,代表着 ,即 ,诶? -
实质上就是对
先加 (就是修改量),然后和 取 。这一操作我们是熟悉的...由 V,如果其的询问是 的 ,则我们有一个分段函数合并的 做法,但显然此时我们构造的 是用来求 的区间和的...如果问的是 的区间最值,反而做不了,因为 不一定恰为 。 -
故还是多支付一个
转化成区间加减解决,由此我们有了一个 的做法。历史最大值也一样,只是 变成 罢了(当前减去历史最大值总应该是非正的)。 -
到这我们谈的都是简单加减。显然,对最值操作,也是可以的,只要拆成加减...诶?注意到我们先前之所以是
,是因为总体上发出了一个最值操作。这里我们在把对原数组的最值操作拆成加减操作之后,对 数组的最值操作才发出,且可能是各不相同的(转化成的加减操作的变化量取决于原本的区间最值),故会发出 个最值操作,由此可以看出 层嵌套会导致 次拆解和一次 ,实际效果为 ,不过实际上我并不理解怎么继续嵌套(呃呃,题目里就定义一个 ,然后对 求区间历史最值和?这是手动嵌套呀),且我不理解论文里的 从何而来。 -
不过,到这里,我们的 segbeats 科技,可以说是点完了。剩下的,主要是在实践中运用和积累经验了。撒花?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】