数据结构:根号
\(O(\sqrt{N})\) 或 \(O(\sqrt{N} \log n)\) 数据结构往往能为一些 \(O(\mathrm{polylog})\) 难以维护的操作。
分块基础
P13979 数列分块入门 4
给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及区间加法,区间求和。
解法
线段树/树状数组:\(O(\log n)\)~\(O(\log n)\)
而分块可以做到:\(O(\sqrt{n})\)~\(O(1)\) 或 \(O(1)\)~\(O(\sqrt{n})\),常在修改操作次数和询问操作次数不平衡时使用,常见于莫队之中。
分块的思路:将序列分为 \(\sqrt{n}\) 个大块,每个大块内有 \(\sqrt{n}\) 个元素。两层数据结构。(区别于于线段树的 \(O(\log n)\) 层)。
![[70326cd9-6d14-42c7-9e7b-7c0a95a7c090.png]]
这样的话,一个区间最多涉及到 \(\sqrt{n}\) 大块,以及 \(O(\sqrt{n})\) 个散点元素(在左右的两个散块之中)。
下文中 \(l_x,r_x\) 分别表示第 \(x\) 个块的左端点和右端点,\(bel_i\) 表示下标为 \(i\) 的元素所在的块。
\(O(\sqrt{n})\)~\(O(\sqrt{n})\):修改整块的 \(tag\),将两个散块将 \(tag\) 下放并暴力修改散点元素。
\(O(\sqrt{n})\)~\(O(1)\):
首先将询问改成询问两次前缀和并差分。
考虑维护 \(Sum_i\) 表示前 \(i\) 个大块的前缀和,以及 \(sum_i\) 表示 \([l_{bel_i},i]\) 的前缀和。询问 \([1,i]\) 的前缀和即为 \(Sum_{bel_i -1} + sum_i\)。
考虑修改对 \(Sum\) 和 \(sum\) 数组的影响。其中 \(Sum\) 数组可以直接暴力枚举并修改,而修改对整块的 \(sum\) 数组的影响可以直接打 \(tag\),散块则可以暴力修改。
当然,询问 \([1,i]\) 的前缀和的值会变成 \(Sum_{bei_i - 1} + sum_i + tag_{bel_i} \cdot (i - l_{bei_i} + 1)\)。
\(O(1)\)~\(O(\sqrt{n})\):
类似上文,自行思考。
一些讲的比较好的博客:
分块(块状数组)(从入门到出门右拐) - 知乎
类线段树操作
由于分块可以视作只有两层的线段树,所以在线段树上常用的技巧也是可以在分块上用的。
P13982 数列分块入门 7
给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及区间乘法,区间加法,单点询问。
解法:
类似线段树地,可以对每个块打两个 \(tagadd_i\) 以及 \(tagmut_i\)。加法操作整块时会修改 \(tagadd_i\),乘法操作修改整块时会修改 \(tagmut_i\)。单次修改和询问时间复杂度均为 \(O(\sqrt{n})\)。
定期重构动态数组
P13981 数列分块入门 6
题目背景
洛谷的数列分块入门系列的测试数据范围和原题有不同。
题目描述
给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及单点插入,单点询问。测试数据随机生成,方式见数据范围限制。
输入格式
第一行输入一个数字 \(n\)。
第二行输入 \(n\) 个数字,第 \(i\) 个数字为 \(a_i\),以空格隔开。
接下来输入 \(n\) 行询问,每行先输入 \(1\) 个数字 \(\mathrm{opt}\),以空格隔开。
若 \(\mathrm{opt} = 0\),则在这一行继续输入 \(2\) 个数字 \(l, r\),表示在第 \(l\) 个数字前插入数字 \(r\)。
若 \(\mathrm{opt} = 1\),则在这一行继续输入 \(1\) 个数 \(c\),表示询问 \(a_c\) 的值。
输出格式
对于每次询问,输出一行一个数字表示答案。
解法
其实解法可以不依赖于随机。
在随机数据下,我们直接将插入的元素加入到对应块即可。可以利用 vector 维护每个块,然后直接 \(O(B)\) 插入(\(B\) 是插入块的大小)。由于随机数据下接近均匀随机,所以单次修改和询问时间复杂度期望为 \(O(\sqrt{n})\)。
在数据非随机的情况下,可以考虑每插入 \(\sqrt{q}\) 次,则进行一次重构。这样总共只会重构 \(\sqrt{q}\) 次,单次重构时间复杂度为 \(O(n+q)\)。而单次操作插入的时间复杂度为 \(O(\sqrt{n} + \sqrt{q})\)。
总时间复杂度即为:\(O((n+q)(\sqrt{n}+\sqrt{q}))\)。
时间复杂度平衡
P13977 数列分块入门 2
题目背景
洛谷的数列分块入门系列的测试数据范围和原题有不同。
题目描述
给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及区间加法,询问区间内小于某个值 \(x\) 的元素个数。
输入格式
第一行输入一个数字 \(n\)。
第二行输入 \(n\) 个数字,第 \(i\) 个数字为 \(a_i\),以空格隔开。
接下来输入 \(n\) 行询问,每行输入四个数字 \(\mathrm{opt}\)、\(l\)、\(r\)、\(c\),以空格隔开。
若 \(\mathrm{opt} = 0\),表示将位于 \([l, r]\) 的之间的数字都加 \(c\)。
若 \(\mathrm{opt} = 1\),表示询问 \([l, r]\) 中,小于 \(x=c^2\) 的数字的个数。
解法
已经超越 polylog 数据结构能维护的范围。
询问:散块直接查询。对于整块,我们需要维护出块内元素排序后的数组,并在该数组上二分即可。时间复杂度 \(O(\sqrt{n} \log n)\)
修改:散块推平 \(tag\) 并暴力修改元素,再重新对该块排序。整块直接修改 \(tag\) 即可。时间复杂度 \(O(\sqrt{n} \log n)\)。
注意到修改散块的时间复杂度为 \(O(\sqrt{n} \log n)\),修改整块的时间复杂度为 \(O(\sqrt{n})\),我们可以考虑平衡这两个部分的时间复杂度。
不妨考虑得更加细致。假设每段块长为 \(B\),则修改散块的时间复杂度为 \(O(B \log B)\),而修改整块的时间复杂度为 \(O(n/B)\)。
不难发现,在 \(B=\sqrt{n/ \log n}\) 时两者取得平衡,单次修改/查询的总时间复杂度即为 \(O(n \sqrt{n \log n})\)。
类似的题目:
- 洛谷 P3863
莫队 + 分块
P4396 [AHOI2013] 作业
题目描述
此时己是凌晨两点,刚刚做了 Codeforces 的小 A 掏出了英语试卷。英语作业其实不算多,一个小时刚好可以做完。然后是一个小时可以做完的数学作业,接下来是分别都是一个小时可以做完的化学,物理,语文……小 A 压力巨大。
这时小 A 碰见了一道非常恶心的数学题,给定了一个长度为 \(n\) 的数列和若干个询问,每个询问是关于数列的区间表示数列的第 \(l\) 个数到第 \(r\) 个数),首先你要统计该区间内大于等于 \(a\),小于等于 \(b\) 的数的个数,其次是所有大于等于 \(a\),小于等于 \(b\) 的,且在该区间中出现过的数值的个数。
小 A 望着那数万的数据规模几乎绝望,只能向大神您求救,请您帮帮他吧。
输入格式
第一行两个整数 \(n,m\)
接下来 \(n\) 个不超过 \(10^5\) 的正整数表示数列
接下来 \(m\) 行,每行四个整数 \(l,r,a,b\),具体含义参见题意。
解法
本质上是一个静态离线三维数点/动态离线二维数点,故可以使用 CDQ 分治/树套树解决。
考虑莫队做法,我们需要在莫队的中途维护一个树状数组。时间复杂度为 \(O(m\sqrt{n} \log n)\)。
考虑如何把 \(\log n\) 去掉。由于本质上会有 \(O(m\sqrt{n})\) 修改,但只有 \(O(m)\) 次查询。这提示我们可以使用 \(O(1)\)~\(O(\sqrt{n})\) 的分块来维护该一维区间问题而非树状数组。总时间复杂度即可优化为 \(O(m \sqrt{n})\)。
区间众数
经典分块题目,可以感受到分块数据结构的魅力。
P13984 数列分块入门 9
题目背景
洛谷的数列分块入门系列的测试数据范围和原题有不同。
题目描述
给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及询问区间的最小众数。
输入格式
第一行输入一个数字 \(n\)。
第二行输入 \(n\) 个数字,第 \(i\) 个数字为 \(a_i\),以空格隔开。
接下来输入 \(n\) 行询问,每行输入两个数字 \(l\)、\(r\),以空格隔开。
表示查询位于 \([l,r]\) 的数字的众数。
解法
首先对数组离散化。
考虑如何为区间内 \(x\) 出现的次数。可以维护 \(S_{i,j}\) 表示前 \(i\) 个块 \(j\) 的出现次数。这样就可以 \(O(1)\) 查询连续整块中 \(x\) 的出现次数。
不难发现,如果我们提前预处理每个整块的众数,那么 \([l,r]\) 的众数要么在散块内出现,要么是某个整块的众数。这样我们就只有 \(O(\sqrt{n})\) 众数的候选人,对每个候选人都可以均摊 \(O(1)\) 求出它出现的次数。
单次询问时间复杂度 \(O(\sqrt{n})\),总时间复杂度 \(O(q\sqrt{n})\),空间复杂度 \(O(n\sqrt{n})\)。
分块的题目会比 polylog 数据结构多变很多,以下是一些题目推荐:
- CodeChef FNCS
- 洛谷 P3203
- 洛谷 P4117
- 洛谷 P6774
树分块
没啥用。
可以看看 树分块 - OI Wiki 或者下发的 slide。
带修莫队(三维莫队)
本质上是将二维上的莫队转化为了三维莫队。
可以参见 带修改莫队 - OI Wiki。
P1903 【模板】带修莫队 / [国家集训队] 数颜色 / 维护队列
墨墨购买了一套 \(N\) 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:
-
\(Q\ L\ R\) 代表询问你从第 \(L\) 支画笔到第 \(R\) 支画笔中共有几种不同颜色的画笔。
-
\(R\ P\ C\) 把第 \(P\) 支画笔替换为颜色 \(C\)。
为了满足墨墨的要求,你知道你需要干什么了吗?
解法
莫队本质上是在一个二维平面上有 \(n\) 个点 \((x_1,y_1), \ldots (x_n,y_n)\),我们每次能走到离当前点曼哈顿距离为 \(1\) 的点,希望能花尽量少的步数经过少的点。
莫队证明了存在构造方法使得步数不超过 \(O(n\sqrt{n})\) (假设 \(1 \leq x_i,y_i \leq n\)),即单块内 \(x\) 坐标改变不超过 \(\sqrt{n}\),\(y\) 坐标递增,总共有 \(\sqrt{n}\) 个块。
而带修莫队引入了第三维时刻 \(t\),则由二维平面变成了三维空间。
而三维空间的构造类似:将坐标分成 \(n^{1/3}\) 块,单次 \(x,y\) 的变化不会超过 \(n^{2/3}\),总共有 \(n^{1/3} \cdot n^{1/3} = n^{2/3}\) 块。总时间复杂度为 \(O(n^{5/3})\)。
不难发现在 \(k\) 维下莫队的时间复杂度为 \(O(n^{2-1/k})\),与 kD-Tree 类似,实际也只在 \(k \leq 3\) 时有意义。
回滚莫队 & 二次莫队离线
来不及备课了,自学一下吧(或者晚点讲)。

浙公网安备 33010602011771号