各种数据结构问题
各种简单数据结构维护的 polylog 题
P11094 [ROI 2021 Day 2] 砍树
比较有思维量的数据结构题。
对于这一类区间操作统计合法点数的问题,不妨考虑对点计数而非模拟过程。不妨求出 \(L_i\) 表示 \(i\) 向左倒下,需要询问的 \(l\) 的最大值;\(R_i\) 表示 \(i\) 向右倒下,需要询问的 \(r\) 的最小值。
考虑 \(L\) 的求法。考虑由从左到右求 \(L\),每个位置由近及远砍树:维护变量 \(j\) 表示当前考察的需要砍掉的数的下标。显然有 \(a_i-h_i<a_j\)。考虑若 \(a_j+h_j\leq a_i\),由于其右侧所有树均已经被砍倒,可以令其往右倒下,接着考虑 \(j-1\);否则砍倒 \(j\) 需要 \(L_j\) 及右侧的全部被砍倒,直接考虑 \(L_j-1\)。由于 \(L\) 数组有包含关系,我们显然只需要考虑 \((a_i-h_i,a_i)\) 区间内所有满足 \(a_j+h_j> a_i\) 的点中 \(L\) 的最小值,并将其与原始范围取 \(\min\)。第二个条件可以通过把点挂在 \(a_j+h_j\) 处消去,于是使用线段树维护单点修改区间最小值即可。\(R\) 的求法是类似的。
当我们求得 \(L,R\) 之后,对于每一个询问 \(l,r\),一个合法的 \(i\) 需要满足 \(l\leq L_i\leq i\leq r\) 或 \(l\leq i\leq R_i\leq r\)。考虑一个容斥,我们分别求出有多少个小于等于 \(r\) 的 \(i\) 满足 \(L_i\geq l\);有多少个 \(\geq l\) 的 \(i\) 满足 \(R_i\leq r\);有多少个小于等于 \(r\) 的 \(R_i\) 满足 \(L_i\geq l\)。分别离线下来用树状数组维护即可。
P7880 [Ynoi2006] rldcot
支配对。树上 LCA 相关问题时,考虑枚举 LCA,有效的点对数可能很少。
对于这道题来说,考虑合并子树时产生的影响。我们考虑启发式合并维护子树节点,插入一个点 \(x\) 时,显然只有 \(x\) 标号的前驱 \(pre_x\) 和后继 \(nxt_x\) 与 \(x\) 组成的两个区间 \([pre_x,x],[x,nxt_x]\) 是有效的,因为其余区间包含这两个区间的某一个,若它们会产生贡献,则这两个一定也可以产生贡献,而数颜色只需要一个贡献就够了。
于是你得到了 \(O(n\log n)\) 个三元组 \((l,r,c)\) 表示若询问区间包含 \([l,r]\),则其中存在颜色 \(c\)。考虑把 \(l,r\) 放在平面上,询问 \(L,R\) 要问的是 \(l\ge L,r\le R\) 的点的颜色数。询问是一个 2-side 矩形,考虑 \(l\) 从大到小扫掉一维,此时每种颜色只有 \(r\) 最小的有效。于是对于每种颜色维护当前最小的 \(r\),再对每一个 \(r\) 维护有多少种颜色以他作为最小值位置,需要支持单点加减、前缀求和,使用树状数组即可。
总复杂度 \(O(n\log^2n+q\log n)\)。
P8528 [Ynoi2003] 铃原露露
仍然是支配对。考虑一组点对 \((x,y,z)\) 其中 \(z=lca(x,y)\),他会对合法子区间做出一些限制。例如当 \(a_x<a_y<a_z\) 时,\(l\le a_x\) 且 \(r\in [a_y,a_z-1]\) 的子区间一定不合法,其他情况同理。也就是提出了若干个矩形,将其覆盖为不合法。
考虑在 LCA 处合并子树时,插入一个 \(a_x\),不难发现可以只保留 \(a_x\) 的前驱和后继,其他的子区间会被这两种覆盖,于是是 \(O(n\log n)\) 个矩形覆盖,可以差分成 3-side 矩形覆盖;查询是一个 2-side 矩形覆盖。于是可以维护区间加减,从大往小扫描 \(L\),查询一个前缀历史上为 \(0\) 的个数和。对于历史和问题,考虑维护一个 add 和一个 attach 的 tag,其中 attach tag 表示给历史个数加上这个区间最小值的个数(这里不记为“给为 \(0\) 的加”的原因是合并 tag 后,第一个 tag 值为 \(0\) 的点其值会变化,就没法合并标记了;但最小值在合并了 add 标记后仍然不变),每次 pushdown 判断两个子区间最小值是否是大区间最小值,是再下方 attach 标记即可。
P11364 [NOIP2024] 树上查询
多个限制下的查询问题,一定要考虑找多个限制的共同变量进行维护,这样就可以把多个限制缩成一个区间查询的形式。
首先,我们希望求出每个点作为 LCA 时的所有极长标号连续段。不妨做一个类似 dsu on tree(基于轻重链分治的那个算法,若做启发式合并会多一个 log),对于每个点,先遍历其所有子节点计算子节点答案,同时维护一个并查集,表示哪些标号的连续段已经找到了 LCA。注意这个并查集无需清空。所有儿子都走完后,枚举每一个轻子树内的点,观察其所在连通块左右两侧的点是否也在当前点的子树范围内,是则合并,此时可以得到 \(O(n)\) 个连续段(每次合并都会消除一个空隙)。这部分总复杂度是 \(O(n\log n)\)。
然后问题变为:你有若干个线段 \([L,R,v]\),其中 \(v\) 是其权值。每次询问给定一个 \((l,r,k)\),求与 \([l,r]\) 交集至少为 \(k\) 的线段中,权值的最大值。考虑分类讨论:
- 若 \(L\le l\le r\le R\),此时不必考虑 \(k\) 的限制,把每一个线段挂在 \(L\) 上,扫描左端点维护右端点,等价于查询 \([r,n]\) 内的权值最大值。
- 若 \(L\le l\le R\le r\),此时有额外限制 \(l\le R-k+1\)。考虑扫描 \(k\),则可以去除 \(L\le l\) 的限制(此时同样考虑了 \(l\le L\le R\le r\) 的情况),问题变为查询所有 \(l\le R-k+1\) 且 \(R\le r\) 的线段的权值最大值。不难发现需要满足的条件就是 \(R\in[l+k-1,r]\),仍然是区间维护最大值。
- 若 \(l\le L\le r\le R\),此时有额外限制 \(r\geq L+k-1\)。仍然扫描 \(k\) 去掉 \(R\) 的限制,此时需要满足的条件就是 \(L\in [l,r-k+1]\),仍然是区间维护最大值。
因此做三次扫描线即可。复杂度 \(O(n\log n)\)。
QOJ964. Excluded Min
首先考虑如何判定:对于一个区间,我们维护一个值域的桶,设 \(sum_i\) 表示 \(\leq i\) 的数的个数,找到最后一个 \(sum_i\geq i+1\) 的位置 \(x\),则答案为 \(x+1\)。于是可以做 \(O(qa)\)。由于这是值域的桶,用莫队配合线段树可以做 \(O(n\sqrt n\log n)\)。
考虑 \(\operatorname{polylog}\) 做法。由于询问和值域密切相关,于是考虑换维,从大往小扫描值域,维护每个询问当前的 \(sum\) 的值。初始时的 \(sum\) 为区间长度,每次去掉一个数后,需要给包含这个数的询问减 \(1\)。每次删完一个值的数后,找满足条件的位置。一个位置首次满足条件即可求得答案。
现在的问题是怎么找包含这个数的所有区间。注意到若两个区间是包含关系,则大区间答案一定不小于小区间,所以可以等大区间确定答案之后再维护小区间,这样在线段树上的有效值左右端点均单增,减法的受众就是一个区间了。考虑如何求出一个区间删除后,新出现的不被包含的区间有哪些。若给所有区间按照左端点增、右端点降的顺序排序,设被删除的区间编号为 \(x\),已被加入而未被删除的下一个区间的编号为 \(y\),上一个区间的右端点为 \(p\),则新区间的编号一定在 \((x,y)\) 内。我们每次找到该区间内右端点最大(相同的,左端点最小,也即编号最小)的未加入区间 \(j\),若 \(r_j>p\) 则该区间合法,令 \(y\gets j\) 之后继续寻找即可。对于一个区间,我们开一个 BIT 动态维护当前值域下每个位置的值有没有被删掉,即可求得其当前的权值。
P9130 [USACO23FEB] Hungry Cow P
对于一类靠后元素的贡献受到靠前元素的情况影响的问题,不妨可以考虑兔队线段树(单侧递归)。其中最经典的是维护前缀最大值的个数。具体做法是在每个区间维护只考虑这个区间内的元素时,区间内的答案(有时可能需要维护右区间的答案),然后利用一个 calc 函数在 pushup 时 \(O(\log n)\) 地计算。
对于此题同理。我们实现一个 \(calc(lst,le,ri)\),表示将 \(lst\) 的干草传入 \([le,ri]\) 区间后的实际贡献。设原始数据天数为 \(t\),传给下一个区间的数量为 \(lst\),考虑两种情况:
- 若 \(t_{le}+lst>mid-le+1\),此时左区间一定被填满,重新计算 \(lst\) 后递归进入右区间即可。
- 否则右区间一定不动,递归进左区间计算即可。8672
动态开点线段树即可。由于信息有可减性,故不必维护右区间答案。
QOJ70. Bitaro, who Leaps through Time
对于这种对描述过程的序列区间询问的问题,有几种思考方式:
- 找到一个比较形式化的策略,然后用数据结构/扫描线/换维等方式求得答案(QOJ964、P9839);
- 找一个事件发生的充要条件、观察区间单调性转化问题,统计事件发生数目来求解(P11191、P11094);
- 直接暴力上数据结构维护过程量。
- 直接暴力换维,扫描操作对象维护询问答案。(如在询问区间开始时插入一个元素表示询问,加入一个操作对象时修改所有元素的询问,在某个询问区间结束时即可回收询问得到答案)(QOJ8672)
- 线段树分治。
对于这个题,你发现我们在每一个位置都是用一个时间去对冲一个时间区间,得到一个新的时间,显然有公共的过程量,因此可以直接上数据结构维护过程量。
首先,我们考虑令每一个时间都减去坐标,这样消去了在两个点之间移动的自然花费,便于讨论。注意反向走的时候要将减改成加。
接着,我们设两个状态量 \((L,R)\) 和 \((s,t,c)\),表示这一段的位置可以于 \([L,R]\) 内的任意时间出发而无障碍地通过;或我们为了最小化花费,这一段一定是从 \(s\) 时刻出发,于时刻 \(t\) 走完,且花费为 \(c\)。分类讨论可以写出任意两种状态的合并式子,每一次询问的初始状态是 \((b,b,0)\),终止状态要补合并一个 \((d,d,0)\)。
注意到这个合并一定是最优的,且具有结合律,于是上线段树维护即可。复杂度 \(O(n\log n)\)。
QOJ8672. 排队
考虑扫描人,用线段树同时维护所有询问当前的人数。对每个询问,走到 \(L\) 的时候加入线段树,将初始的 \(-\infty\) 设为 \(0\),走完 \(R\) 后查询答案。每加入一个人,需要给人数在 \([l_i,r_i]\) 区间内的人数 \(+1\)。不难发现每个区间的人数变化不会发生相对大小变化,于是按照询问 \(L\) 倒序在线段树上重标号,则有人数单调不降,每次线段树上二分+区间修改即可。
P11286 [COTS 2017] 盗道 Krimošten
首先,同一个区间内,终止值关于起始值存在单调性,因此可以直接暴力维护分段函数,查询时在每个区间二分,复杂度 \(O(n\log^2 n)\),可以通过。
考虑优化这个做法:我们可以想到扫描线扫序列,线段树以起始值为下标,维护其运行到当前位置时的值。不难发现这个东西时刻单调,可以二分+区间修改解决。考虑怎么回答询问?仍然离线,当扫到 \(l\) 的时候,二分找到当前值为 \(l\) 的位置,扫到 \(r\) 时查询这个位置上的值即可。总复杂度来到 \(O(n\log V)\)。
P11340 「COI 2019」TENIS
我们观察到,序列开头的那个人一定能赢;若一个人 \(x\) 能赢,则对于所有在某一个场地能战胜 \(x\) 的人都能赢。因此,所有能赢的人在三个序列里的位置都是一段前缀,这一段前缀的人的集合相同。我们希望找到最靠前的一个位置,使得三个序列里对应的集合相同。
首先可以考虑异或哈希,对于每个位置,维护前缀上三种不同的哈希值,并维护最大值与最小值的差值。问题出现在这样的修改是一个区间异或,没法维护最小差值。或许可以和哈希?但是错误率有点大,维护也略显复杂。
考虑另外一个判定思路:对于每个选手维护最靠前和最靠后的排名 \(le_i,ri_i\),则 \([le_i,ri_i-1]\) 内显然都不能作为合法位置;若一个位置没有被任何一个 \(le_i\) 覆盖,则它一定是一个前缀集合相等的合法位置。因此问题转化为区间加、找最靠前的全局最小值的位置。线段树即可做到 \(O(n\log n)\)。
P10144 [WC2024] 水镜
考虑一个间隔两侧柱子高度的大小关系:
- \(h_i>h_{i+1}\),则当 \(L<\frac{h_i+h_{i+1}}{2}\) 时,\(\min_i<\min_{i+1}<\max_{i+1}<\max_{i}\),记为 \((14,23)\);否则 \(\min_{i+1}<\min_{i}<\max_{i}<\max_{i+1}\),记为 \((23,14)\)。
- \(h_i<h_{i+1}\),则当 \(L<\frac{h_i+h_{i+1}}{2}\) 时形如 \((23,14)\);否则形如 \((14,23)\)。
- \(h_i=h_{i+1}\) 时,\(\min_i=\min_{i+1}<\max_{i+1}=\max_{i}\),记为 \((=)\)。
于是可以对 \(L\) 离散化出 \(O(n)\) 个值。注意到 \(L=\frac{h_i+h_{i+1}}{2}\) 时一定不优,故考虑每个值取成 \(v+eps\) 的实际值。另还需取 \(0\)。
对于一个合法区间的所有间隔,其模式一定形如 \((14,23),(14,23),\dots,(14,23),(=),(23,14),(23,14),\dots,(23,14)\) 的形式,中间的等号可以缺失。也即:\((23,14)\) 和 \((=)\) 的后面都只能接 \((23,14)\),\((14,23)\) 后面不限。
对于不同的扫描维度,有两种考虑方式:
- 从小往大扫描 \(L\),维护合法的极长连续段,每一次只会改 \(O(1)\) 个这样的连续段,可以用 set 维护。另开一个线段树维护以 \(i\) 为左端点的最大右端点,每次得到一个新的连续段时,就做区间对值取 \(\max\),最后单点查询即可。
- 从大往小扫描左端点(间隔),线段树维护每个 \(L\) 取值对应当前能取到的最大右端点。假设当前扫描到间隔 \((i,i+1)\),根据当前间隔和下一个间隔两侧的大小关系,可以划分出至多 \(3\) 个 \(L\) 的区间,每个区间中两个间隔取得相同的模式。其中,某些区间对应的模式是不合法的,也即不能再往后延续,分类讨论,将这样的区间推平成 \(i+1\) 即可,每次修改完毕后查询全局最大值贡献答案。
上述两种做法复杂度均为 \(O(n\log n)\),可以通过。第二种做法不需要使用 set,常数相对更小且实现相对更为容易。
P11536 [NOISG2023 Finals] Curtains
扫描线,考虑扫到 \(r\) 时加入所有右端点为 \(r\) 的区间,在每个点上维护“只使用 \([l,r]\) 的所有帘子,能到的最靠右的位置。回答询问是容易的,考虑添加一个区间 \([l,r]\),这使得所有 \([1,l]\) 中 \(\geq l-1\) 的值都变成 \(r\)(也可以是对 \(r\) 取 \(\max\))。设计 tag \((x,y)\) 表示对区间内 \(\geq x\) 的做对 \(y\) 取 \(\max\),考虑两个 tag \((l_1,r_1),(l_2,r_2)\) 的合并,设后一个是新来的,则我们一定有 \(l_1<r_1,l_2<r_2,r_1\leq r_2\)。感觉性质很多,考虑分类讨论:
- 若 \(l_2\le l_1\),则直接 cover。
- 否则,\(l_1<l_2\)。若 \(r_1\geq l_2\),则合并结果是 \((l_1,r_2)\)。
- 否则,\(l_1<r_1<l_2<r_2\)。注意到位置 \(i\) 能被 tag \((x,y)\) 覆盖的条件是 \(i\le x+1\),且 \(i\) 的初始值为 \(i-1\),此时有 \(l_2>l_1+1\geq i>i-1\),也即某位置的初始值一定不可能被 \(l_2\) 更新到。又其上方的 tag 单调不降,因此所有位置实际最大值为 \(r_1\),第二个 tag 是无效的,直接舍去。
于是可以用线段树维护了。复杂度 \(O(n\log n)\)。
P11295 [NOISG2022 Qualification] Dragonfly
不妨考虑处理出一个池塘最后一次有虫是在 \(t_i\) 次询问之前。然后考虑时光倒流,每次加入一个点,就删除掉所有子树内同色点的贡献,可以对每个点用一个 set 进行维护当前有贡献的点,那么就是子树加、单点查询,可以直接使用 BIT。
考虑求 \(t_i\),容易想到整体二分,仍然是单点加、子树求和,使用 BIT 维护,常数比较小。总复杂度 \(O(q\log^2 n)\),瓶颈在整体二分。
根号相关
P8078 [WC2022] 秃子酋长
回滚莫队:对于左端点在同一个块内的所有询问,初始化左右端点都在块的右端点处,每次询问暴力扩展左侧,均摊扩展右侧,回答完一个询问后,左侧回滚,右侧不变。
暴力做法是直接莫队,用 set 维护元素,插入或删除都查找前驱后继即可。
考虑去掉一个 log。注意到在只有删除操作的时候,使用链表可以 \(O(1)\) 地找到前驱后继,所以考虑回滚莫队,右端点从大往小排序,每一块重构从左端点到 \(n\) 的完整链表,每次询问左右端点分别删除,回答后左端点回退(撤销)即可单根号。
P5386 [Cnoi2019] 数字游戏
考虑对较为困难的值域做莫队,问题转化为 \(n\sqrt n\) 次单点 flip,\(O(n)\) 次查询一个区间内有多少个子区间全为 \(1\)。显然可以套一个分块做平衡。那么我们可以扫描散块的每个点和每个完整块,需要做的只是快速查询一个完整块前驱后继上的连续 \(1\) 长度,以及块内独立连续段的答案。考虑在修改的时候维护这个信息。具体地,我们对于每一个块独立考虑,对于每一个连续段,在段尾维护段首的下标,在段首维护段尾的下标,则 \(0\to 1\) 合并一个段是容易的,这对应莫队的插入。合并后,根据这个新段是否不在边上,可以直接维护出新的答案,于是做到上述信息的 \(O(1)\) 查询。这个东西不支持删除,于是回滚莫队即可。最后做到单根号。
P4117 [Ynoi2018] 五彩斑斓的世界
考虑分块,对于这种把一个值的全部变成另一个值,考虑用并查集维护值,初始为 \(x\) 的元素的当前值为根的对应值。查询时散块暴力扫,整块在并查集的过程中额外维护每个数的出现次数即可。
考虑修改,散块修改显然可以暴力修改每一个位置的值,并撤销之前的所有并查集/桶修改,暴力重构。对于整块,考虑均摊,维护最大值 \(mx\)。若 \(mx\geq 2x\),则把小于等于 \(x\) 的全部加 \(x\),再打一个区间减法标记;否则直接遍历大于 \(x\) 的部分,暴力减去 \(x\) 即可,不难发现遍历的量和 \(mx\) 的变化量一致。于是可以做到 \(O(n\sqrt n\alpha(n))\)。
注意到每一块的操作是独立的,于是可以对于每一块分别做,即可解决桶开不下的问题。
注意如下两个细节:
- \(0\) 存在时会导致一个被改走的值重复出现,因而需要特判 \(0\) 的个数,其他操作时不考虑 \(0\)。
- 并查集的下标会有 \(tag\) 引起的位移。牢记并查集维护的信息:下标为 \(x\) 的点的根的下标为 \(y\),代表初始值为 \(x\) 的点经过修改后的当前实际值为 \(y-tag\)。则每次将实际值为 \(x\) 合并到 \(y\) 上时,在并查集的操作是将下标 \(x+tag\) 的点合并到下标 \(y+tag\) 的点。但查询一个点的值时,用其初始的真实值直接作为下标查根。换句话说,虽然都是用下标表示的,但是初始值和最后的真实值的偏移独立,初始值不偏移。
困难的题
P6109 [Ynoi2009] rprmq1
题意:给定 \(n\times n\) 的矩形,有 \(m\) 次矩形加,在所有修改结束后有 \(q\) 次矩形 max 查询。\(n,m\le 5\times 10^4,q\le 5\times 10^5\)。
CDQ 分治可以把没有可减性的区间询问转成前缀询问,有可减性的可以直接差分。
考虑对 x 轴做 CDQ 分治,把每个询问挂在最小的包含它的分治区间 \([l,r]\) 上,从 \(mid\) 处分成两个前缀矩形查询。于是一个单独的问题可以用区间加区间历史最大值解决。
考虑不重复遍历所有询问,注意到每一层的子问题形成若干个不交的区间,在一个子问题区间的左端点处,需要消除前面的历史最大值的影响,将前面的历史最大值设成当前的区间最大值。可以使用一个 tag 解决,也可以执行区间加 \(\inf\) 解决。需要 \(O(\log n)\) 次遍历矩形的 y 轴和所有修改,总复杂度 \(O((n+m)\log ^2n+q\log n)\)。
区间加区间历史最值:维护 tag 表示该区间当前还需加多少,及还需加的这个值历史最值是多少即可。若有赋值操作,需分开维护复制前后的加 tag 的历史最值。
P9247 [集训队互测 2018] 完美的队列
考虑离线,对于每一次 push 求出其加入的 \(x\) 的生效时间段,最后将同一个值的时间段取并集即可简单求出每个时刻的答案。
每一次 push 生效的是一个区间,因此考虑分块。设块长为 \(B\)。考虑先对每一个块预处理出 \(t_i\) 表示从第 \(i\) 个时刻开始计算,到哪一个时刻满足块内的每一个 \(i\) 都满足加上的数 \(d_i\geq a_i\)。维护每一个值剩余的阈值,及阈值的桶,使用双指针求值,每次需要支持加入或删除一次操作的影响。对于完整覆盖的操作,使用全局 \(tag\) 即可;对于散的修改,暴力改即可(这里有一个均摊,暴力次数为 \(O(mB)\))。此部分总复杂度为 \(O(m(B+\frac{n}{B}))\)。
考虑一次操作,其覆盖的整块可以直接利用预处理的信息得到答案;对于散点,是形如 \(O(mB)\) 个询问,每次给定一个点 \(x\),问该点从 \(t\) 开始首次被覆盖 \(a_x\) 次的时间,同时有区间覆盖。考虑换维扫描线,扫描序列轴维护时间轴,将区间覆盖差分成两个点的操作,每个点上都会对时间轴的一个后缀做操作。对于一个询问,只需要二分首个从 \(x\) 开始的前缀和 \(\geq a_x\) 的位置即可,这显然可以转化为全局二分一个前缀。可以使用线段树或树状数组上二分完成。此部分复杂度 \(O(mB\log m)\),取 \(B=\sqrt{\frac{n}{\log n}}\) 即可做到 \(O(m\sqrt{n\log n})\)。
树状数组上二分:对全局和大于或小于一个界进行二分,从高往低确定答案的每一位,并给答案补上这一位即可。原理是树状数组上 \(i\) 保存的是 \((i-lowbit(i),i]\) 的和。
QOJ8035. Call Me Call Me
题意:有 \(n\) 个人,第 \(i\) 个人参与聚会的条件是 \([l_i,r_i]\) 中至少有 \(k_i\) 个人参加了聚会,问能参加聚会的人数最大值。\(n\le 4\times 10^5\)。
显然可以转化为给所有覆盖 \(x\) 的点减 \(1\),以及求全局最小值。先考虑所有区间都是一个前缀的情况,此时将所有人按照右端点离散化,等价于区间减、全局最小值,不难完成。
对于其余情况,考虑猫树分治,利用 CDQ 结构转为前缀情况,此时一个区间由一个前缀和一个后缀拼合形成。对于每一个分治树的节点,我们开两颗线段树(一个表示前缀的限制,一个表示后缀的限制),仍然把这个点上的所有人按照某一端点离散化后挂在线段树上。此时我们的限制是,同一个人在两棵树上的值的和 \(\geq k_i\),使用折半报警器在 \(O(\log n)\) 的复杂度下转化为寻找值为 \(0\) 的位置即可。
具体地,我们在分治树上同样 pushup 维护每个线段树的最小值的 min(类似树套树),可以花费 \(O(\log n)\) 的代价找到一个 \(0\),同样在 \(O(\log n)\) 的代价内完成限制的均分,总 \(0\) 的个数不超过 \(O(n\log n)\),故此部分复杂度 \(O(n\log^2 n)\);确定一个人参加后,所有 cover 了这个人的节点都可能进行修改,共有 \(O(\log n)\) 个,需要在每棵树上进行区间减。故整道题总复杂度做到 \(O(n\log^2 n)\)。
另解:考虑一个暴力,即每次暴力找到覆盖一个点的区间去减,可以借用线段树结构把每个 \([l,r]\) 挂在 \(\log\) 个点上,但不报警,直接把减的挂在每一个点上面,复杂度是 \(O(\sum len)\)。
这样复杂度太大。考虑设定阈值 \(B\),只在线段树上保存剩余目标数小于等于 \(B\) 的点。这样一个点在线段树上会被遍历到 \(B\) 次,此部分复杂度为 \(O(n\log n+nB)\)。此时限制数大于 \(B\) 的某些点是没有被更新的,因此我们需要每找到 \(B\) 个加入的人之后,都把所有未更新的点都重更新一次,可以使用前缀和快速更新,此部分复杂度 \(O(\frac{n^2}{B})\),于是可以平衡做到 \(O(n\sqrt n)\)。
P7220 [JOISC 2020] 掃除
依照部分分提示,首先考虑点单调不增的情况,此时无论如何操作,点之间的相对位置关系不变,直接二分到修改区间然后做区间推平即可。可以用线段树或平衡树维护。
再考虑没有插入点的情况。此时有一个性质:任意时刻满足至少被推了一次的所有点,满足当前坐标单调不增。可以用反证法简单证明。于是考虑快速求出每个点首次被推的时间及推到的位置,注意到一个点能被覆盖的矩形的横轴长度是一个区间,于是这是一个简单的静态区间 min,随便做即可。在每个点被操作之后再将其插入数据结构一起维护,用平衡树即可。这里复杂度是 \(O(Q\log Q)\)。
考虑插入点。注意插入点的操作会破坏上述性质,也即上述性质的成立条件是初始时所有点均存在。考虑线段树分治去掉插入操作。具体地,每一个询问与对应点的插入时刻形成一个区间,将其拆分成 \(\log Q\) 个区间,DFS 整个线段树,在每个节点上做一次没有插入的问题,即可算出询问的每一个点执行完该区间中的操作后,所在的位置。将位置存下来,留到下一次有该询问的点上利用即可。复杂度 \(O(Q\log^2 Q)\)。
P7560 [JOISC 2021] フードコート (Day1)
整个队列不能直接维护,并且队首有一些人离开很麻烦。首先考虑去掉离开操作:假设你知道一次询问时,该店已经离开的人数为 \(l\),则去掉离开操作后,实际询问的人是 \(B+l\)。因此我们需要求出每个询问时,该店已经离开的人数。直接动态维护人数也不方便,考虑有离开人数=总的到来人数-当前人数,我们考虑同时维护两个值。总到来人数可以直接区间加,当前人数需要支持区间加、区间减、区间对 0 取 max,同时单点查询。注意此处对值取 max 没有和区间求和共存,故不需要吉司机线段树,直接维护 tag 即可。
具体地,由于查询是单点的,可以用线段树维护当前区间内的人数最小值。具体地,我们维护两个 tag,分别是区间加/对一个数取 max,假定是先加减,再取 max,对区间取 max 操作可以直接合并;对加减操作,就将两个 tag 同时加上当前要加的值即可。合并 tag 的时候,假设原有 tag 为 \((a,b)\),新 tag 为 \((c,d)\),合并之后就是 \((a+c,\max(b+c,d))\)。这是容易维护的。
此时,问题变为有若干个区间加正数的操作,多个询问,问某个点首次大于等于一个值的时间。类似前面说的,考虑换维扫描线,扫描线扫序列,维护时间轴。一个区间加操作等价于在 \(l\) 位置,给一个时间的后缀加上了一个数,在 \(r+1\) 的位置减去了一个数。需要维护的操作是区间加、维护区间最大值,二分首个大于某个值的位置。线段树是好做的。最后就是找那一次操作加的是哪个颜色即可,若最终答案晚于询问时间,即为 -1。
P7907 [Ynoi2005] rmscne
注意到扫描线只能解决三维的问题(扫一维,数据结构下标一维,维护一维的最值或和值),对于四维问题,NOIPT4 的解决方案是放缩限制后去掉一维(把区间看成二维平面上的点,对面积容斥);此外还可以考虑是否某两维相对独立,拆成两个三维的问题进行解决,一个问题用数据结构维护,另一个问题用查询技巧解决。
题意:\(q\) 次询问一个区间 \([L,R]\) 的最短子区间 \([l,r]\) 长度,使得子区间的颜色数等于询问区间的颜色数。
考虑一个更紧的问题:要求 \(r=R\) 时,我们可以直接二分到一个最小的 \(l\),使得对于任意 \(i\in[L,l)\),均有 \(nxt_i\le R\)。线段树上维护 \(nxt\) 的最大值即可。我们设这个问题下,询问 \([x,y]\) 得到的结果为 \(res_{x,y}\)。
利用这个东西,我们可以去掉原问题中的 \(l\) 这一变量。具体地,我们要求 \(\min_{r=p}^R res_{L,r}\),其中 \(p\) 表示最靠前的满足任意 \(i\in(p,R]\),均有 \(pre_i\geq L\) 的点,仍然可以线段树维护 \(pre\) 的最小值求出。
直接考虑 \(\min\) 式子的求法。考虑扫描 \(res\) 和询问的共有变量 \(L\),在线段树上维护 \(r=i\) 时的 \(res\),查询是一个区间最小值;考虑新加进来一个 \(L\) 时,\(\geq nxt_L\) 的右端点 \(r\) 的 \(res\) 不变;\([L,nxt_L)\) 内的所有区间都必须扩展到 \(L\) 处,\(i\) 处的答案是 \(i-L+1\),实际上是一个公差为 \(1\) 的等差数列。于是做区间赋值等差数列、区间求 min 即可。总复杂度 \(O((n+q)\log n)\)。
P9877 [EC Final 2021] Vacation
最大子段和模型:在线维护全局内形如 \(\max(a_i+b_j)\) 的值,类似 CDQ 的在线版本,合并时考虑继承子区间或从左右分别选择一个端点。
题意:给定序列 \(a\) 和常数 \(c\),\(q\) 次询问进行单点修改,或询问 \([l,r]\) 内长度不超过 \(c\) 的最大子段和。
这个 \(c\) 的限制很奇怪,考虑哨兵 trick,以 \(c\) 为块长分块,则答案要么在某一块内,要么是一块的后缀加上下一块的前缀。
对于答案在块内的情况,对每一块开线段树维护最大子段和,再开一个全局线段树维护其最大值即可。
考虑拼合的情况。给每一块的元素分别标号,设 \(pre_i\) 表示当前块内前 \(i\) 个元素的和,\(suf_i\) 表示后缀和。注意到长度不超过 \(c\) 等价于 \(pre_i+suf_j\) 中满足 \(j>i\)。注意到单点修改等价于给所属块的 \(pre\) 和 \(suf\) 做区间加减,于是使用对于每一块使用线段树维护 \(pre,suf\) 分别的最大值,以及从区间内分别选出 \(i\) 和 \(j\) 的最大答案即可。合并是容易的。仍然另开线段树维护每一组相邻块的答案的 \(\max\)。
考虑一个查询。特判掉不跨块的情况,剩余答案有如下几种贡献形式:
- 完整块内,直接在第一个全局线段树上查询区间 max。
- 完整块与完整块的间隔,在第二个全局线段树上查询区间 max。
- 散块与整块。以散块在前为例,此时 \(j\) 要求是一个后缀,分 \(i\) 也在这个后缀内,和 \(i\) 不在后缀内两种情况查询,前者在对应块的线段树上做区间查询,后者由于不交,直接查两个 \(\max\) 后拼合。散块在后是类似的。
- 散块与散块,此时 \(j\) 要求是一个后缀,\(i\) 要求是一个前缀,两者不交的情况是容易的;若有交,按照 \(i\) 是否在交里分类,其余与 3 类似。
于是做到单 log,常数较大。
P9388 [THUPC 2023 决赛] 先人类的人类选别
多树二分:最基本的应用是求区间第 \(k\) 大时,在两个主席树上同时二分。类似推广,对多个序列的某些区间合成一个新序列,对其做二分,即可用类似方法做多树二分。
对于一个前缀,操作等价于使用 \(x\) 替换掉区间最小的那一个值。将询问差分成两个前缀和,每次询问等价于把一个 \(x\) 序列的前缀和一个 \(a\) 的前缀的元素放在一起,问这些元素前 \(k\) 小的和,扫描操作序列,用权值线段树维护已有 \(x\),用权值主席树维护 \(a\),每次在两棵树上做多树二分即可。
P10107 [GDKOI2023 提高组] 树
向子树内最右侧儿子倍增,与同层子树的前缀结构相结合。
具体地,是树上向子树内的倍增、同层子树间的差分与前缀和,及前缀子树内深度上的差分,用来解决子树邻域问题。
对于此题来说,异或与加法在一起只能拆位,而向子树内的倍增恰好与拆位相吻合。而后有几个关键转化:
- 直接记录答案,在合并时可能会因为有多个儿子导致复杂度爆炸。于是对同一层子树的答案做前缀和累加,每次只需要从最右侧的一个儿子处继承答案即可。回答询问时只需要把两个前缀答案相减。
- 在合并时,由于距离信息上增加了一个 \(1\),故下层的答案继承上来会有变化。你需要知道有多少个点从 \(1\) 变成了 \(0\),也即需要知道小于某一个深度范围内的前缀子树点中,有多少个点的权值为 \(1\)。于是再叠加一层对深度范围的累加,转化为对所有深度的前缀子树点计数,只需要处理第 \(2^k\) 级最右儿子即可相减求得答案。
树套树与分治
P9068 [Ynoi Easy Round 2022] 超人机械 TEST_95
题意:单点修改,全局本质不同逆序对数。
将本质不同逆序对进行一个转化。考虑对于若存在数对 \((a,b)\) 是一个逆序对,那么不妨选择第一个 \(a\) 和最后一个 \(b\) 作为计数对象。进而,对于每个数 \(x\),记录其最先出现的位置 \(fir_x\) 和最后出现的位置 \(lst_x\)。我们希望求出 \(i<j\) 且 \(lst_i>fir_j\) 的数对 \((i,j)\) 的数量。不带修可以扫描线简单解决。
带修时,考虑增加时间维,每一个时间会导致 \(O(1)\) 个 \(fir\) 或 \(lst\) 发生变化。因此我们记录每一个 \(fir\) 和 \(lst\) 的出现时间,而删除可以通过附加 \(1\) 和 \(-1\) 的权值实现。注意到对两个 pair 的存在时间同时做偏序是困难的,因此我们考察每一个时间的修改量。以一个 \(lst\) 的修改 \((t,lst_x,1)\) 为例,我希望求出所有时间 \(<t\),下标 \(j>x\) 且 \(fir_j<lst_i\) 的 \(j\) 的带权个数。其余情况类似。这是一个三维偏序问题,在较小的空间限制下直接使用 CDQ 分治,先按时间排序,再按下标双指针,最后按 \(fir\) 和 \(lst\) 的值做树状数组即可。时间复杂度 \(O(n\log^2 n)\),空间复杂度 \(O(n)\)。
P7476 「C.E.L.U-02」苦涩
注意到插入信息不能合并,不能表示为 tag 下传,故标记永久化类似物是显然需要的。在每个线段树的节点上维护一个堆,额外再维护区间最大值,那么插入和求最值的操作就好维护了。考虑删除。在标记永久化的前提下,遍历到的每一个点的最大值显然都不大于删除的目标值。对于一个节点,若其不被操作区间包含,则往下递归,注意若堆顶的数是删除目标,需要将其下放到子儿子里面去(可能删的就是这个)。对于包含的情况,若该堆里有,则删除后 return;否则通过 check 最大值判断两个儿子内是否存在删除目标,若有则递归下去删。
这里的复杂度显然是均摊的。注意到插入操作插入的元素个数是 \(O(n\log n)\) 的,而删除操作遍历不被包含的节点总数是 \(O(n\log n)\),故堆里的总元素个数是 \(O(n\log n)\) 级别的,下去删的复杂度均摊做到 \(O(n\log^2 n)\),这也是总复杂度。
P5445 [APIO2019] 路灯
每次单点修改只会导致一个矩形内的路径合法状态改变。一个直接的想法是维护每个点保持状态的时长,但修改的时候需要给答案做对位加,比较困难。考虑每个点加上的是一段区间长度,不妨差分成两个后缀,这样一个矩形内加减的值就相同了。也即在修改后,维护此刻到最后一刻假设该点状态不变的答案,查询时单点查询,状态为合法时减去剩余时间即可。使用 set 维护合法连续段,剩的是一个矩形加、单点查,差分成 \(4\) 个前缀的 2-side 矩形之后转单点加、后缀查,树状数组套动态开点线段树即可。时空 2 log。

浙公网安备 33010602011771号