分块相关杂谈

command_block

2020-06-03 16:41:48

Personal

\text{stO lxl Orz}

您将在此看到 OIer 分块的最低水平。

0. 分块概论

在数据结构问题中,我们需要维护一些离散的信息单位,单位的数目会影响处理的复杂度。

如果信息支持不受限制的合并与批量处理,那么就可以使用线段树等区间数据结构。

当然,有时并不存在高效的信息合并化简方式,而需要在“批量”和“零散”之间找到平衡点,这就是分块的核心思想。

这一类问题的性质更弱,所以问题形式变化多端,“整散”关系多种多样,具有传统数据结构难以比拟的灵活性。

1. 基础数列分块

在这一节中,我们将认识一些经典的数列分块问题,初步熟悉分块的形式和思想。

下面统一约定数列长度为 n ,块大小为 B

如图,在分块中有如下概念 :

找出所有的 整块 和 散块内元素 是容易的。

在这个问题中,信息能够高效合并,这使得分块没有太大的优势。

但是,从这一类简单问题入门分块比较易于初学者理解。

两者的复杂度均为 O(n/B+B) ,令 B=\sqrt{n} 得到最优复杂度 O(q\sqrt{n})

思考我们这样做的本质,不难发现,其实是将线段树这一 “二叉树” 改成了 “\sqrt{n} 叉树” 。

上面的做法对应标记永久化,其实也可以将标记下推,即每次暴力清空两个散块的标记,复杂度不变。

评测记录 | 代码Link

B=\sqrt{n\log n} 得到最优复杂度 O(q\sqrt{n\log n})

复杂度分析技巧 : 分块的主要思想是平衡,若复杂度由两部分组成,直接令两者相等,即可解得最优块大小。

(本题数据非常弱,错误做法可能可以通过)

类似上一题,对每个块维护有序序列,查询时整块二分,散块暴力。

复杂度 O(q\sqrt{n\log n})

评测记录

对每个块维护有序序列,查询时二分转化成 P2801。复杂度为 O(q\sqrt{n\log n}\log w) ,无法通过。

仔细分析查询时的操作 : 整块二分,散块暴力。

根据信息论,整块二分难以优化。但对散块进行 O(\log w) 次暴力有些浪费。

可以事先将散块归并,这样对散块的查询无需暴力遍历,而可以一次二分搞定。

这样,单次询问的复杂度改进为 O\big((n/B)\log B\log w+B \big)

若将 \log w 视为与 \log n 同阶,则 令 B=\sqrt{n}\log n 可得复杂度为 O(q\sqrt{n}\log n),卡常后可以通过。

注意,散块部分的常数较大,应适当调小块大小。

评测记录

: 觉得不过瘾可以做 P3380 【模板】二逼平衡树(树套树)

“行动步数”问题和经典的求算贡献问题有很大不同。

不难发现,若没有修改,容易 O(n) 求出各个点的答案。

对于一个块,预处理其每个元素走出该块所需步数,以及到达的点。

这样,可以 O(1) 跳过一个块。查询复杂度改进为 O(n/B+B)

在修改时,也只需 O(B) 重构一个块。

B=\sqrt{n} 可得复杂度为 O(q\sqrt{n})

评测记录

操作二可以直接把涉及到的两个块重构。

区间加时,散块可以暴力重构,但是整块只能打标记。

每个位置的贡献可以看做关于标记的一次函数,于是维护凸壳即可。

散块重构时利用归并可以 O(B) 建立凸壳。

查询时,由于标记只会递增,线性扫即可。

B=\sqrt{n} 可得复杂度为 O(q\sqrt{n})

此时贡献是不独立的,对每个块分别考虑并不方便。

利用桶记录每个数的出现次数(奇偶性),即可增量维护好数的个数。

s[i,j] 为块 [i...j] 中好数个数,可以 O(n/B) 次从各个块开始增量维护得到,复杂度为 O(n^2/B)

o[i][x] 表示前 i 个块中 x 的出现次数(奇偶性),通过差分,可以得到任意块区间中某个数的出现次数。

查询时,若 [l,r] 包含的整块区间为 [L,R] ,则先取出 s[L,R]

然后考虑散块的影响,仍然用增量法计算即可。

总复杂度为 O(nB+n^2/B) ,令 B\sqrt{n} 可得复杂度为 O\big((n+q)\sqrt{n}\big)

若要节省空间,可以用 bitset 记录 o ,则空间可以做到 O(n\sqrt{n}/w)

可以发现,本题思想类似于回滚莫队。实际上,静态分块的功能是莫队的子集,所以强制在线时分块才有优越性。

基本操作和上一题非常类似。

贡献不独立。

用桶记录每个数的出现次数(奇偶性),即可增量维护众数。

s[i,j] 为块 [i...j] 中的众数,利用增量求解。

o[i][x] 表示前 i 个块中 x 的出现次数。

查询时,若 [l,r] 包含的整块区间为 [L,R] ,则先取出 s[L,R]

然后考虑散块的影响,仍然用增量法计算即可。(注意散块对出现次数也有贡献)

时空复杂度均为 O(n\sqrt{n})

在上一题中,空间的瓶颈是 o 数组。观察其用途 : 判定散块中某个数在询问区间中的出现次数是否 >k

我们可以用 vector 记录下同类数的所有出现位置。

当判定散块中数 u 出现次数是否 >k 时,假设 u 是询问区间内同类数的最靠左出现(左侧散块,右侧散块类似),则检查 u 后面的第 k 个同类数是否在询问区间外即可。

空间复杂度改进为 O(n) ,时间常数也减小了。

在逆序对问题中,每一对数都有贡献,贡献模式是二维的。

询问时,答案的贡献成分如下图 :

绿色是整块内部的贡献,橙色是散块内部的贡献,红色是整块和散块之间的贡献,蓝色是散块之间的贡献。

考虑对每个块内前后缀都预处理内部逆序对数,容易使用树状数组做到 O(n\log n),这样就解决了橙色部分。

考虑归并排序的过程,不难两个区间之间的逆序对可以将这两个区间归并计算。

于是,预处理块内有序序列,蓝色部分就可以(先取出有序散块)归并计算。

然后预处理 s[i,j] 表示块 i...j 内部的逆序对数。

考虑差分,有 s[i,j]=s[i+1,j]+s[i,j-1]-s[i+1,j-1]+(\text{块}i,j\text{之间的贡献})

两块间贡献仍然可以 O(\sqrt{n}) 归并计算,则预处理复杂度为 O(n\sqrt{n})。绿色部分完成。

接下来预处理 o[i][x] 表示前 i 个块中 \leq x 的数的个数,然后可以计算红色部分(对每个散块元素分别统计贡献)。

复杂度为 O(n\sqrt{n})。此做法常数较大。

还有另一个常数较小的做法,预处理 f[i][j] 表示 [1...i] 中的数与第 j 个块之间的贡献,利用桶(值域前缀和)不难做到 O(n\sqrt{n})

这样,计算两块之间的贡献时,直接差分即可(不需要常数巨大的归并)。

计算红色部分时,可以转而枚举整块,然后差分。

蓝色部分依然需要归并计算。

2. 经典分块技巧

一些 基于分块的技巧 和 分块需要的技巧。

某些分块可以视作三层的 \sqrt{n} 叉树,如图。

块状数组查询和修改的复杂度一优一劣,用于查询和修改数目不同的问题,以平衡复杂度。

当值域大小为 O(n) 时,可以用类似权值线段树的形式维护 “权值 \sqrt{n} 叉树”。这个技巧被称为值域分块

将区间分块。维护 c[k][i] 表示在第 k 块中第 i 个位置被覆盖了多少次。

这样,在单点修改之后,能 O(\sqrt{n}) 地计算出各个块的和的变化。

查询时,对于散块暴力 O(\sqrt{n}) 次区间求和,使用 O(\sqrt{n}) 修改 O(1) 求和的分块即可。

将上述三层 \sqrt{n} 叉树可持久化即可。可以搞出“块状主席树”之类的东西。

例题 : P5574 [CmdOI2019]任务分配问题 ( O(n\sqrt{n}+nk\log n)

解决一系列带插入的序列问题。(在带插入的序列上维护分块)

将某个元素插入某个块时,将该块重构。若某个块的大小超过 2B (或其他自定常数)则分裂为两个大小为 B 的块。

这样,分裂次数是 O(n/B) 的,且能保证各个块的大小在 [B,2B] 之间。

代码实现细节较多,具体见例题。

在远古时期这题的时限是 \texttt{1s} ,故几乎只有实现较好的分块能通过。

先考虑带单点修改区间K小值,若沿袭 P5356 的做法,复杂度为 O(q\sqrt{n}\log n) ,无法接受。

注意到本题的特殊性 : 值域较小(认为与 n 同阶)。这启发我们使用值域分块。

维护序列块的前缀权值块状数组,差分能得到任意一端块的权值块状数组。再将散块 O(B) 建立一个权值块状数组。

在线性组合后的权值块状数组上,模仿主席树二分来爬,复杂度是 O(\sqrt{n}) 的。

在单点修改时,只会有 O(n/B)O(1) 的权值块状数组更新。

B=\sqrt{n} 即可做到 O(n\sqrt{n})

接下来考虑插入,只需多考虑分裂操作。

评测记录 | 代码Link

题意 : 维护一个序列,支持(在指定元素后面)插入元素,询问两个元素的先后顺序。

显然可以使用平衡树维护,回答询问时,求出两者的排名并比较。这样能做到 O(\log n) 修改 O(\log n) 查询。

考虑给每个元素一个标号,是的先后顺序和标号的大小顺序相对应。

给根一个值域区间,如 [0,1][0,2^{60}-1],将点 u 的权值设为自己区间的中点 mid=(u.l+u.r)/2,并令左右儿子的值域区间分别为 [u.l,mid],[mid,u.r]

显然,这样得到的权值也满足左小右大的性质。若平衡树的最大高度为 h ,则权值精度要求是 O(2^h) 的。

使用重量平衡树(如替罪羊树)来维护这个序列,这样能够保证我们每次操作更新(并重新标记权值)的子树大小是 O(\log n) 级别的。

而且,替罪羊树的树高严格 \leq \log_{1/\alpha} n (一般不超过 3\log n),故对精度的要求也较低(低于 O(n^3)),使用 doublelong long 即可满足。

比较先后顺序时,只需比较标号即可,这样即可做到(均摊) O(\log n) 修改 O(1) 查询。

将序列分成块状链表的形式,每一块的大小在 [\log n,2\log n] 之间,适时分裂。

块外用替罪羊树维护。共产生 O(n/\log n) 次分裂,故复杂度为 O(n)

比较时,先比较所在块的先后顺序,再比较块内的先后顺序。

块内使用链表维护,插入的元素的权值设置为前驱和后继权值的平均数,这样所需的值域也并不大。

该问题的一个经典做法是 \rm ST 表,但需要 O(n\log n) 的预处理时间。

考虑将原序列分为 O(\log n) 一块,每一块预处理前/后缀 \min ,整块的 \min 上建立 \rm ST 表。

这样,若查询的区间端点异块,则使用散块前/后缀 \min 拼上一段整块的 \min 就能做到 O(1) 查询。

上面的复杂度是 O(n)-O(1) 的。

若区间端点同块,只能暴力,但是题目中一般不会出现这种情况。这样,我们就得到了一个简易的 O(n)-O(1)\ \rm rmq 替代品。

递归套用本算法可以得到 O(n\alpha (n))-O(\alpha (n)) 的做法,但不实用。

3. 根号分治 & 自然根号

有时,根号复杂度会产生于一些非常自然的问题中。

例 : 有若干数和为 n ,则最多有 n/a 个数字大于 a

若对于某个数可以 O(x) 处理,那我们就以 O(xa) 的复杂度保证了其余数都 \leq a

点度数总和是 O(m) 的,对度数进行根号分治。

对于度数 \geq \sqrt{m} 的点,称为大点,反之称为小点。

在查询小点时暴力。修改时主动给周围的大点贡献,由于大点数目是 O(\sqrt{m}) 的,一次修改也不会超过 O(\sqrt{m})

总复杂度 O(q\sqrt{m})

考虑 x\geq \sqrt{n} 的询问,显然可以 O(\sqrt{n}) 计算。

对于剩下每个询问 (x,y) ,都维护其答案。在单点修改时,对于每个 x 都有一个 y 的答案改变,复杂度也是 O(\sqrt{n})

显然,最差情况是 n=1+2+3+...+m ,和是 O(m^2) 级别的,故 m=O(\sqrt{n})

证明 : 我们选出复杂度最大的 q 个不同的询问进行求和。

S 从大到小排序后的结果为 S' ,复杂度最大的 q 个不同的询问的复杂度和为 O\Big(\sum_{i=1}^{\sqrt{q}}S_i\times i\Big)

最差情况是 SO(\sqrt{q})O(n/\sqrt{q}) ,此时复杂度为 O(n\sqrt{q})

例题 : P5576 [CmdOI2019]口头禅

证明 : 对于某个 S_i ,其贡献的复杂度显然为 O\big(\min(|S_i|^2,n)\big)

|S_i|>\sqrt{n} 时,这样的串只有 O(\sqrt{n}) 个,就算跑满 O(n) 的复杂度,也仅为 O(n\sqrt{n})

|S_i|\leq \sqrt{n} 时,复杂度不超过 O(\sum |S_i|^2)\leq O(n*\max|S_i|)=O(n\sqrt{n})

也存在达到此上界的构造,可见 Itst : 广义 SAM 上每个模板串包含的节点数量和的上界以及构造

例题 : [Str记录]CF204E Little Elephant and Strings

证明 :

也存在达到此上界的构造,限于篇幅故不展示。

找出这些等差序列的方法暂不介绍,有题目之后再说。

$\rm Border$ 长度按照 $[2^0,2^1),[2^1,2^2),...[2^k,2^{k+1})...$ 分段后,可以证明每一段内至多只有一个等差序列。 故公差 $>B$ 的等差序列总大小是 $O(\sum_{k}(n/2^k)/B)=O(n/B)$。 例题 : [[Str记录]Loj#6681. yww 与树上的回文串](https://www.luogu.com.cn/blog/command-block/str-ji-lu-loj6681-yww-yu-shu-shang-di-hui-wen-chuan) ## 4. Ynoi 分块选做 题解都较为繁琐,故不在同一篇文章中展示。 - [[DS记录]P5063 [Ynoi2014] 置身天上之森](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p5063-ynoi2014-zhi-shen-tian-shang-zhi-sen) - [[DS记录]P5611 [Ynoi2013]D2T2](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p5611-ynoi2013d2t2-post) - [[DS记录]P5073 [Ynoi2015] 世上最幸福的女孩](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p5073-ynoi2015-shi-shang-zui-xing-fu-di-ru-hai) - **最初分块** : [[DS记录]P4119 [Ynoi2018]未来日记](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p4119-ynoi2018-wei-lai-ri-ji) - **第二分块** : [[DS记录]P4117 [Ynoi2018]五彩斑斓的世界](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p4117-ynoi2018-wu-cai-ban-lan-di-shi-jie) (大分块入门推荐) - **第四分块** : [[DS记录]P5397 [Ynoi2018]天降之物](https://www.luogu.com.cn/problem/P5397) - **第四分块·改** : [[DS记录]P5692 [MtOI2019]手牵手走向明天](https://www.luogu.com.cn/problem/P5692) - **第六分块** : [[DS记录]P4118 [Ynoi2018] 末日时在做什么?有没有空?可以来拯救吗?](https://www.luogu.com.cn/problem/P4118) - **第十分块** : [[DS记录]P6578 [Ynoi2019]魔法少女网站](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p6578-ynoi2019-mo-fa-shao-ru-wang-zhan)