SDWC day2 - 分块和莫队
分块
最简单的分块模型:
对于 一个问题规模 \(n\) ,我们将其划分为大小为 \(s\) 的块(共 \(\frac{n}{s}\) 块)。
对于每个块内部,我们设法预处理贡献。
对于每一个询问,我们首先将其拆成整块部分与零散部分:整块部分我们设法直接利用先前的预处理快速得到答案。零散部分我们用暴力求得答案。
复杂度通常为 \(\mathcal O(a \cdot n \cdot s + b \cdot \frac{n^2}{s})\)。
复杂度平衡
通过调整 \(s\) 的大小,使得总复杂度尽可能低。(显然 \(s\) 不一定一直未 \(\sqrt n\))
一般情况下,两部分相等时总体复杂度最低。(考虑均值不等式 \(a + b \ge 2 \sqrt {ab}\))
实际操作时可能需要调整参数。
例:当 \(a = \mathcal O(\log n), b = \mathcal O(1)\) 时,\(s\) 取 \(\sqrt {\frac{n}{\log n}}\) 时,总复杂度最低,为 \(\mathcal O(n \sqrt {n \log n})\)。(注意 \(\log n\) 的位置)
简单的复杂度平衡理论
一般用于两种操作次数不同的情况。
- \(O(\sqrt n)\) 的区间修改,\(O(1)\) 的单点查询。
- \(O(1)\) 的单点修改,\(O(\sqrt n)\) 的区间求和。
(如果这里有一个做法,需要 \(n \sqrt n\) 次修改,\(n\) 次查询,显然选后者)
差分/前缀和之后可以把上述“区间”和“单点”对调。
如果都是区间呢?
可以参考树状数组维护区间加和区间求和。(待填坑)
特殊说明
分块不只有序列分块,有时还有对值域分块的操作。
还有一种情况是一个类似于分类讨论的东西:对设计到的元素根据 \(\le \sqrt n\) 和 \(> \sqrt n\) 分成两类,对这两类分别采用不同的处理方式。一个经典的例题是:无向图三元环计数 LG-P1989
zyb 给的例子是求划分数:将 \(n\) 拆分为若干个正整数之和的方案数。
设 \(f_{i,j}\) 表示用 \(i\) 个数表示出 \(j\) 的方案数,我们可以添加一个 \(1\) 或者把所有数 \(+1\),有转移方程:
设 \(g_{i,j}\) 表示用 \(\le i\) 的数表示出 \(j\) 的方案数,不用 \(i\),或者至少用一个 \(i\),有转移方程:
这其实是两种方法,但都是 \(\mathcal O(n^2)\) 的。
但我们发现 $ > \sqrt n$ 的数最多有 \(\sqrt n\) 个,因此可以分成两段来 dp。
\(> \sqrt n\) 的部分用 \(f\) 处理,\(\le \sqrt n\) 的部分用 \(g\) 来处理。
记
表示用 \(i\) 个 \(> \sqrt n\) 的数拼出 \(j\) 的方案数。
答案就是
然后复杂度就变成 \(\mathcal O(n \sqrt n)\) 了。
例题 1
给定序列,多次查询区间众数。强制在线。
(其实就是 LG-P4168)
- 做法 1:
因为区间众数很难合并,于是我们用到一个技巧:处理“块到块的答案”。
对于一个查询,可以作为答案的数只有整块部分的众数和两段零散的 \(\sqrt n\) 个数。
设 \(f_{i,j}\) 表示第 \(i\) 个块到第 \(j\) 个块的众数。
设 \(s_{i,j}\) 表示从第 \(1\) 个块到第 \(i\) 个块第 \(j\) 个数出现了几次。
两个都能预处理,对于每个询问就可以结合这两个数组求得。
复杂度 \(\mathcal O(n \sqrt n)\)。
- 做法 2:
值域数组。(zyb 说这是他给我们讲课的前一天刚想到的)
好吧我承认我没听懂(待填坑)
nyy 第二天早上又稍微 yy 了一下,现在是晚上,我又忘了/kx
例题 2
四维数点。给你 \(n\) 个点,每个点有四个属性 \((a,b,c,d)\),对于每个点求:\(4\) 个属性全都小于它的点有多少个。假设所有点的所有属性两两不同。
- 做法 1:
cdq 套 cdq 套 树状数组,复杂度 \(\mathcal O(n \log^3 n)\)。
- 做法 2:
第一维排序。
第二维值域分块。
然后剩下两维可以看做一个强制在线的二维数点。
对每个块按照第三维进行排序,然后将第四位作为 值域/下标 来构建主席树。
对于每次查询一个数,首先通过二分确定第三维的位置,然后通过查询主席树得到第四维的信息。(注意这里的两个 \(\log\) 是相加而非想乘)
对于每次插入一个数,将所有点推倒重构主席树。
总复杂度为 \(\mathcal O(n \sqrt n \log n)\)。
WC2022
开始抄 ppt
对值域分块。
对于一个值域分块来说,所有可能的询问被划分成了 \(O(n)\) 种可能的情况(左右端点位置各有 \(O(\sqrt n)\) 种)
想办法预处理出每种可能的情况的答案。
向集合中插入一个数,并查询前驱后继。
需要 $\log $ 数据结构?其实我们把插入变成删除,就可以用链表实现 \(O(1)\) 操作了。
对于一个询问,只需要枚举值域块,直接统计块内贡献即可。
对于跨块的贡献,维护出每种询问对应快内的最大最小值直接查询即可。
莫队
“优雅的暴力”。
应用:
- 只有查询没有修改。
- 对于一个查询而言,讲一个端点左右移动 \(1\) 位产生的贡献是容易维护的。
只能离线。
好像在一定条件下可以在线?link。
一般写法。
将序列分为大小为 \(s\) 的块。
将所有询问按照左端点所在的块为第一关键字,右端点为第二关键字排序。
处理两个相邻的询问时,直接把端点大力平移过去。
复杂度分析:左端点的移动次数为 \(O(ms)\);右端点最多移动 \(O(\frac{n}{s})\) 个来回,总次数为 \(\frac{n^2}{s}\)。
假设移动一次端点的代价是 \(a\),则当 \(s\) 取 \(\frac{n}{\sqrt m}\) 时,复杂度为 \(O(a \cdot n \sqrt m)\)
注意:这里区分了序列长度 \(n\) 和询问次数 \(m\)。因为有些题 \(m\) 特别大(如 \(10^6\)) 但是仍然跑得过。(理解为 \(O(m \sqrt n)\) 或 \(O((n+m)\sqrt n)\) 是错误的)
并且,单词移动端点的 \(a\) 有时候仍然是复杂度瓶颈,注意到 \(O(n \sqrt m)\) 次修改 \(O(m)\) 次查询可能可以使用上面写的复杂度平衡来消除 \(\log\)。
- 经典例题:
给定序列,多次查询区间内有多少对相同的数。
用桶来记录的话随便做吧。
带修莫队
另加一维时间维。复杂度为 \(O(n^{\frac{5}{3}})\)。
做法:将时间和左端点按照块大小为 \(O(n^{\frac{2}{3}})\) 分块。
将修改和询问按照时间所在的块,左端点所在的块,右端点排序。
二次离线
当你发现 \(O(n \sqrt m)\) 次修改 \(O(m)\) 次查询不太好直接在线处理的时候,可以把这些东西再一次全部离线下来,然后想办法维护。
可以去参考洛谷的模板题?(待填坑)
回滚莫队
不删除/不插入莫队:有时端点向一个方向的移动是容易的,另一个方向的移动是困难的
比如,加边维护连通性是容易的,删边维护连通性很难
此时,可以写一个支持撤销的东西(比如可撤销并查集),然后对于每个左端点的块单独处理
一开始先把左端点固定在块的右侧,然后右端点正常向右移动,左端点每次移动到对应位置,查询完之后再撤回来
不删除莫队经典例题:
给定无向图,多次询问保留编号在某一个区间内的边时,图有多少个连通块
直接 lct
直接不删除莫队+可撤销并查集就解决了(可撤销并差集 待填坑)
WC2022(不插入莫队)
上面提到插入需要 \(\log\) ,一次把插入该为删除,回滚莫队直接做就可以了。
树上根号算法
树上莫队
处理树链问题,常用的操作是打出进出栈序列之后在序列上莫队
树分块
有一种“王室联邦”分块法,将树划分为若干个大小为 \(O(\sqrt n)\) 的块 + 一个关键点
另一种方法
在树上随机撒 \(O(\sqrt n)\) 个点,这样对于任意一个点而言,距离它最近的关键点不超过 \(O(\sqrt n)\)。
定期重构
应用场景
有修改和查询,没有修改时查询的答案是容易计算的,每个修改对每个查询的贡献也是容易计算的。
做法
每 \(O(\sqrt n)\) 次修改操作就彻底重构一次序列,这样一来在相邻两次重构序列之间的查询只需要先在上一次修改的基础上查询答案,然后枚举不超过 \(O(\sqrt n)\) 个修改计算贡献即可。
APIO2019 桥梁
无向带权图,支持修改边权,查询仅保留边权 \(\ge x_i\) 的边时,点 \(y_i\) 所在的连通块大小
如果没有修改怎么做?没有修改是不是离线 + 并查集
在线怎么做?
定期重构
每次处理 \(O(\sqrt n)\) 个操作,然后将整张图刷新一遍。
每一组操作中所有询问面临的图是差不多的——只差 \(O(\sqrt n)\) 条边。
所有可能不相同的边先放在一边不管它,开始把边和询问排序后正常地做最大生成树(事先排一次序即可,无需每次重构都排序)
处理到某个询问时,将这个询问特有的边加进去,查出答案之后再拆掉。
用一个可撤销并查集维护。
考虑到里面藏有一个小 \(\log\) (可撤销并查集),以及两边常数大小不同,可以适当调整一下时间间隔。