分治类算法
CDQ分治:
理解:用一个 \(\log\) 的代价去掉一个维度/一层分治可以代替一个数据结构。
应用:三维偏序:第一维排序,第二维分治,第三维数据结构。
细节:分治遍历顺序与数组何时排序 ?
一般三维偏序采取后序遍历,这样可以保证在划分区间前后两段的时候,是依据 cmp1 划分的。下面演示一下其他遍历顺序(要多排序一下,跑得慢了)。
例题
P4169 [Violet] 天使玩偶/SJY摆棋子、
绝对值不好处理,考虑转化为四个方向讨论正负号来拆点绝对值。
动态加点的二维信息处理可以用在线二维数点类数据结构,比如 KDT,树套树。但是同样可以离线,但是这个时候就需要添加一个维护:时间。进行三维偏序求最值,三维:时间序,\(x\) 轴,\(y\) 轴,
本题要进行四个方向的统计,重点注意本题四个象限分开处理后的写法,以及对于第二维度的处理。由于是四个要分开统计所以要先记录下来统计与修改操作用数组保存再进行第二维的排序。然后调用计算函数四次即可。
P4027 [NOI2007] 货币兑换
不要被题目形式所迷惑,股票题的最佳策略是 allin。
所以必然是一天投入所有钱,另一天卖掉所有持有股票。然后重复多次这个过程。设 \(f_i\) 表示第 \(i\) 天最多持有的钱数。其中
\(cnt_{a}=\dfrac{f_jR_j}{R_j\times A_j+B_j},cnt_{b}=\dfrac{f_{j}}{R_{j}\times A_{j}+B_{j}}\) 表示某天最多可以得到的 \(A,B\) 股票个数。
可以李超线段树优化,不过这里采用斜率优化。
因为式子中有 \(i\) 与 \(j\) 的乘积项,所以考虑斜率优化。
需要维护一个上凸壳,发现斜率和横坐标均不单调怎么办,可以用 \(CDQ\) 分治解决。第一维选取时间维度,第二维选择横坐标,第三维选取斜率。根据状态转移需要,选择中序遍历。由于加点希望按横坐标,查询希望按斜率,故 $ (l,mid) $ 按照横坐标排序,$ (mid+1,r) $ 按照斜率排序。
P3769 [CH弱省胡策R2] TATT
本题是四维偏序可以采用 CDQ 套 CDQ。
第一次 CDQ 结束后根据分治中点打上标记,左边为 \(0\),右边为 \(1\)。这样就处理掉一维。然后就可以再来一个 CDQ 进行三维偏序。统计贡献的时候只有标记为 \(0\) 的才能加入树状数组,只有标记为 \(1\) 的才可以进行答案累加。
注意由于状态转移需要,必须中序遍历,同时不能忽视排序的稳定性,对第二维排序的时候仍然要以相同时第一维优先,对于三四维度排序同理。
整体二分
如果单个询问可以用二分,而操作的贡献是方便减掉的,那么对于多个询问可以使用整体二分。其实就是多个问题一起二分,减少共同部分的计算。
P1527 [国家集训队] 矩阵乘法
这里二维区间 \(k\) 大,可以利用整体二分之后转化为数点。
因为空间限制不大,所以这里的二维数点用二维树状数组来解决。
P4602 [CTSC2018] 混合果汁
注意这里的操作不具有可减性,于是整体二分过程中需要保留前面对后面的影响。
也就是数据结构内的一些信息在对于后续贡献的时候不撤回。
P5163 WD与地图
动态维护删边 SCC 内部 kth 信息。首先把删边变为加边,动态维护加边 SCC,线段树合并维护 kth 信息。问题是我们还是不会维护加边的 SCC 变化,这里可以很巧妙地利用整体二分,check 出每条边最早什么时候能使得其左右两端在同一个 SCC 中。最后离线根据这个时间来合并。
设每条边最后的答案时间是 \(t_e\)。整体二分过程中,假设我们二分的是 \([l,r]\),我们应该保证 \(t_e<l\) 的边的影响已经保留(具体来说我们把 \(t_e<l\) 所连成的在同一个 SCC 内的点缩成一个点),这样子每次做都可以保证单次 tarjan 的复杂度是 \(O(r-l)\)。注意这个缩成一个点的重标号只能对于递归到 \(\rm solve(mid+1,r)\) 的部分起效果,对于 \(\rm solve(l,mid)\) 的部分不能起效果,因为求的是左边往右边的贡献,无法处理自己内部的贡献。
总的时间复杂度 \(O(n\log n)\)。
P6684 [BalticOI 2020 Day1] 小丑
\(\rm Trick:\) 可以发现维护的信息具有交换律。于是禁用一个区间可以转化为将序列复制一遍接在末尾,然后查询 \([r+1,n+l-1]\) 之内的信息和。
发现对于一个左端点 \(i\) 满足条件的最远 \(R_i\) 具有单调性。于是考虑整体二分。
我们用 solve(l,r,tl,tr) 表示处理 \(R_l,R_{l+1}\dots R_r\),且它们的值域在 \([tl,tr]\) 之间,可以通过维护并查集来快速判断是否是二分图。
线段树分治
应用场景:支持动态修改的查询类问题且用于查询的数据结构难以快速删除,或者询问一段时间内(也就是说询问一段时间内加入点的答案)的答案。
算法是在在时间-序列的二维矩形进行操作,正常按照时间一个一个做就是等价于对于时间进行扫描线,而我们可以离线利用线段树维护时间轴。
我们将遇到的情况分成以下两种。修改操作有生效时间(比如加点删点),也就是说修改操作是带撤销的,单点询问。还有一种就是单点修改,询问一段时间区间内的答案。这两种的本质都是对于询问和修改其中一个是时间区间操作,一个是时间轴单点操作。对应一下时间轴单点操作就是时间线段树的叶子节点,时间区间操作就是线段树上分成 \(\log\) 个区间。
于是两种问题的做法都是将区间操作的提前打到线段树的节点上去,用一个 std::vector 保存,然后对于单点的操作,直接遍历根到叶子节点,中途处理经过区间节点的操作。
具体来说对于第一种情况也就是修改操作是一段时间区间的,我们就把修改操作打到线段树的每个节点上,对于一个叶子节点的询问就是把从统计根到叶子节点上所有节点的答案再合并。因为根到叶子的所有节点代表的时间区间都是包含这个单点区间的。
对于第二种询问一段时间区间的就是把询问打到线段树的节点上去,当经过该节点时进行询问,然后对于修改操作,就是在根到叶子节点的所有节点上都进行该修改操作,因为根到叶子的所有区间都包含该修改时间点,都会被影响到。
关于实现方法,其实大部分时候我们并不需要建立真正的线段树,只是用到了该结构而已。如果处理每次修改操作?
根据我们处理的信息是否具有可并性,将方式分为两种。
当然这里不讨论第二种类型的查询,因为你查询一段时间范围的询问,将询问拆开的过程本身就必须保证其具有可并性,否则第一步就不满足了,于是遇到这种类型的我们下文讲的两种方式都适用。
讨论第一种类型,就是在某个时间点提出询问,当信息具有可并性的时候,我们采用本质为树套树的做法,外层的树是对时间轴建立线段树,内层的树是一些数据结构,对于每个节点新建一个数据结构,将涉及的操作都放进去,然后对于子树内的叶子节点上的询问(对应第一种类型)或者该节点上的询问(对应第二种类型)进行查询,最后一个叶子节点的答案就是根到叶子的路径答案并。
当信息不具有可并性的时候(比如图的联通性),我们就不能拆开到每个祖先节点上进行询问了,只能 dfs 到叶子的时候再询问了。这个时候在离开这个节点的时候需要用到数据结构的回退,于是必须满足数据结构的可回退性,所以用到的数据结构必须支持回退,比如要是用并查集的话就只能启发式合并了,不能路径压缩。
P5787 二分图 /【模板】线段树分治
首先是经典扩展域并查集判定二分图,然后套一层线段树分治来支持边的出现一段时间。
P5631 最小mex生成树
先考虑暴力枚举答案 \(x\),将所有权值为 \(x\) 的边删去,查看连通性。
但是发现每次只修改一部分边,其他大部分边都是不变的却每次都要枚举到,可以不可以合并重复操作呢。这就启发我们用 \(\rm solve(l,r)\) 表示边权为 \([l,r]\) 之内的边都不加入。这样在处理 \([l,r]\) 中的边的时候就可以不重复处理 \([0,l-1] \cup [r+1,V+1]\) 中的边了。每次先把右边的边全部加入,然后处理左半边,再撤回,加入左半边的边,处理右半边,再撤回。最后在叶子节点处查询即可。
同时注意细节,本题求的是 \(\rm mex\),所以边权需要从 \([0,\max w+1]\) 都要考虑。
P5443 [APIO2019] 桥梁
操作的时间顺序,使得我们想到线段树分治算法。但是可持久化并查集或者克鲁斯卡尔重构树不具有回退性,所以用不了线段树分治,但是可以用 \(KDT\) 分治,其中一维是可持久化。现在还没太掌握,以后再更。
下面介绍遇到不可回退数据结构的处理方法,根号重构,也叫操作分块。
考虑两种算法,一个是直接修改,暴力查询连通性。我们会感觉到每次图的联通性每次的反复查询似乎有些重复,本着减少重复计算的原则,可以通过对查询边权升序排序,然后按照边权升序大小依次加入这样只需要维护一个并查集即可。对于没有修改的边,可以直接一边查一边加入,而对于有修改的边则需要每次查询都修改一遍。发现此时修改操作又重复了,如何平衡查询次数与修改次数之间的关系呢,我们可以分块!快外保存修改,块内维护一个并查集每次重新修改。
P4585 [FJOI2015] 火星商店问题
这道题就上面说的第二种类型的线段树分治,我们需要查询一段时间内的答案。
本题有时间和区间两个维度同时要求最大异或值。
解法一:最直接的想法就是线段树套字典树,先用线段树解决区间问题,再用字典树解决最大异或值,时间问题直接在字典树节点上打标记即可。
解法二:线段树分治+可持久化字典树。用线段树分治处理时间序,用可持久化字典树完成区间异或最大值。
其实综合解法一二会有一个疑惑就是为什么不能可持久化字典树加时间标记呢?原因是用可持久化处理区间信息一般要有可减性(毕竟是 \(val_r-val_{l-1}\) 凑出来的区间嘛),而时间标记不具备可减性!
QOJ8074. Multiply Then Plus
令 \(y=a_i\times x+b_i\),那么 \(b_i=(-x)\times a_i+y\)。
如果没有修改,对于不同 \(x\) 全局查询。那么就是维护一个在 \(b_i-a_i\) 的坐标系中维护一个上凸壳。每次对于斜率 \(-x\) 进行二分即可。如果改为先对 \(-x\) 排序,再离线查询可以去掉二分改用指针维护做到线性。
如果是区间的话,可以用线段树维护凸包,每次将询问分裂为 \(\log\) 个询问取最大值。同理如果每次二分的话,是 \(O(n\log^2n)\) 的。但是我们还是可以对于 \(-x\) 排序,然后对于线段树每个节点维护一个指针,表示上次扫描到哪里了,每次移动指针就行了。可以做到 \(O(n\log n)\)。
本题有了修改操作可以进行线段树分治。可以发现本题是对于单一时间点询问,但是修改操作有生效时间。于是我们将每个询问都打到对应时间点的叶子节点到根的每个点上,然后将修改操作拆分成 \(\log\) 个放到对应节点上,每到一个节点就建立一颗线段树然后处理对应询问即可。时间复杂度 \(O(n\log^2 n)\)。
QOJ8703.天气预测
想不到吧,这也可以线段树分治,如果去思考动态 dp 的话就坠机了。
考虑从儿子向父亲合并是一个 NTT 分治或者 多项式 ln \(+\) exp 的形式,不好动态去做。
使用线段树分治维护时间轴,每个点开一个动态开点线段树,维护该时间区间内为 \(1\) 的概率,注意没有 pushup,因为只有叶子节点处的概率值才有意义。
在树上向上转移就代表线段树合并的过程,这里由于儿子之间是相互作用的,所以我们应该用多个线段树同时合并。
鸽鸽鸽,抱歉退役之前来不及补了,大学有时间再完善这题题解。
启发式分裂
序列分治,面对不均匀分裂的时候,必定有大有小,我们扫描短的一侧即可!
当然处理树上联通块分裂信息也可以使用类似思想。
P4755 Beautiful Pair
分治处理区间 \((l,r)\) 发现肯定是区间中的点跨过最大值点对区间另一边的点产生贡献。
然后在以最大值点为划分分别计算两边答案。但是题目极端数据可能让最大值点的分布不均匀,导致时间复杂度退化。
于是我们可以在每次选择最大值点后,扫描较段的一边,较长的有一边用数据结构查询。此时可以在线主席树,也可以离线树状数组。这样子复杂度就正确了。
XYD4290.笛镭特
给定一棵点权互不相等的树,每次操作对于所有联通块删除当前联通块的最大值/最小值的点,不断分裂联通块。问多久能删完这棵树,其中奇数轮删最小点,偶数轮删最大点。
可以用线段树维护 dfs 序来快速查询一些子树极值信息。我们改变处理顺序,用 \(\rm solve(u,dep)\) 表示当前处理 \(u\) 子树且操作论数为 \(dep\)。每次找到该子树内未被删除的极值点 \(v\),删除其在线段树上的信息,枚举 \(v\) 的所有儿子 \(s\),\(\rm solve(s,dep+1)\)。最后回来再 \(\rm solve(u,dep+1)\),直到所有点都被删光。很巧妙吧,因为所有联通块是独立的,所以我们可以改变其操作顺序。
概括来说就是每次找到最值之后先对其所有子树处理,而不是同时对于所有分裂出来的联通块处理,处理完一个点后立刻删除其在线段树上的信息,时间复杂度 \(O(n\log n)\)。
还有一种更加普适的方法,之前没见过这个 Trick,就称之为树上启发式分裂吧。
这种树上联通块行走的问题肯定要联系到重心上,这样子才能保证复杂度。
本题由于不是直接跳到重心,所以有点难办。但我们可以对于信息的利用联系到重心上。具体来说我们用 \(\mathrm{solve(u,0/1)}\) 表示处理 \(u\) 的一个联通块,而 \(0/1\) 表示我们是否需要进行遍历处理。初始肯定是要传入 \(1\) 的也就是要预处理,我们算出重心,然后将所有点的信息加入堆中,找到极值点 \(c\),\(u\gets c\)。我们现在删点 \(u\) 点,那就需要处理 \(u\) 的所有相连点下方的联通块。对于所有非重心子树 \(\mathrm{subtree(v)}\),我们进行 \(\mathrm{solve(v,1)}\),并且删点这些子树在堆中的信息。对于重心所在子树,\(\mathrm{solve(v,0)}\),因为我们已经在堆中保存了 \(v\) 子树的信息,不需要重复处理了。但是需要注意由于我们不能遍历该子树,所以新重心不能直接求出,这里很巧妙,直接设老重心为新重心,可以发现需要新处理的子树大小还是 \(\le \dfrac{sz_u}{2}\) 的,只不过这是上一轮的 \(u\),但复杂度还是对的。最后一个细节就是我们可以维护一个 \(to_u\),表示 \(u\) 点往哪个相邻点走可以走到重心,来得到重心和 \(u\) 点相对位置关系。
分治的其他应用
P1429 平面最近点对
根据 \(x\) 坐标排序之后分治,重点是如何求出跨越分治中心的贡献。
设左右两侧递归过来的最小值是 \(d\),将所有和 \(mid\) 的横坐标距离 \(\le d\) 的点都加入点集之中,对于点集进行纵坐标排序,枚举点集中的每一个点,再枚举所有和它纵坐标不超过 \(d\) 的点集中的点,更新答案。首先这两步剪枝都是显然不影响最优化结果的,同时可以证明最后的枚举对于点集中的每个点只会枚举到 \(\le 5\) 个点,所以这是正确的。
P8868 [NOIP2022] 比赛
这里仅介绍 \(52 \rm pts\) 的分治解法,作为最值分治的一种思路。
对于单序列,建立笛卡尔树,然后启发式分裂是很经典的 trick,但是对于双序列,最值点不重合,我们该如何处理?做法是正常进行分治,每次从中点进行分治,然后维护 \(mid+1\to r\) 的前缀极值,和 \(mid\to l\) 的后缀极值。
接下来我们枚举 \(2\times 2\) 最值分布位置,然后进行双指针。比如钦定两个最值都在左边,就拿左边两个后缀极值与右边的前缀极值比较,都成功,才能继续推进,累加贡献。
P8227 「Wdoi-5」建立与摧毁的结界
鸽

浙公网安备 33010602011771号