分块学习笔记

分块是什么

先从经典问题引入:

P3372 【模板】线段树 1

已知一个长为 nn 的数列,你需要进行下面两种操作 mm 次:

  • 将某区间每一个数加上 kk
  • 求出某区间每一个数的和。

对于 100%100\% 的数据,保证 n,m<=100000n,m <= 100000

最朴素的方法:利用循环结构,对数组内每一个数进行单点加和求值。

显然,当每个区间长度都取到最大值的时候,复杂度为 O(nm)\mathcal O(nm),不被我们所接受。

引入分块。

分块,顾名思义就是分成很多块,对于每一块的情况单独处理和维护。

假设我们把一个序列分成很多块,每块长度很小,如果每一次的操作都在一块内,使用前面的暴力做法复杂度是可以接受的。

但是,情况远没有我们想象的这么理想,区间长度总是会很长,其中包含了很多块。

此时,左右部分会有两个块不被完全包含,中间所有的块都被完全包含。

显然,左右部分可以使用暴力,那么中间呢?思考一下...完全包含?我们在线段树中也用过这个方法来保证复杂度,lazytag

对散块,暴力修改或求值,对整块,修改就打 lazytag,求和就把原本记录的整块总和与 lazytag 叠加即可。

假设我们的块长为 bb ,那么显然会有 n/bn / b 个块。

对于两边的散块,我们的最大访问元素数是 b1b - 1 个,总和是 2b22b - 2 个。

对于中间的整块,我们是打 lazytag,只需要访问每一块整体即可,最多有 n÷b2n \div b - 2 个块。

访问次数就是 n÷b+2b4n \div b + 2b - 4

这说明了什么?

我们的复杂度,完全取决于块长。

你块长太小,那么前面的 n÷bn \div b 将会让你飞天,你块长太大,后面的 2b2b 也会让你飞天。

综合之下,我们一般取 n\sqrt{n} 为块长。

例题选讲

分块是需要做很多题来补充自己的卡常方法的。

LOJ #6278. 数列分块入门 2

已知一个长为 nn 的数列,你需要进行下面两种操作 nn 次:

  • 将某区间每一个数加上 cc
  • 求出某区间小于一个数的个数。

对于 100%100\% 的数据,保证 n<=50000n <= 50000

对于散块,我们还是暴力计算。 对于整块,我们需要一些技巧。 排序后二分寻找个数。 复杂度 O(nnlogn)\mathcal O(n \sqrt{n} \log \sqrt{n})

P5356 [Ynoi2017] 由乃打扑克

已知一个长为 nn 的数列,你需要进行下面两种操作 mm 次:

  • 将某区间每一个数加上 kk
  • 求出某区间第 kk 小的数。

对于 100%100\% 的数据,保证 n,m<=100000n,m <= 100000

先二分答案这个值,然后判断是第几小即可。

LOJ #6285. 数列分块入门 9 (https://loj.ac/p/6285)

已知一个长为 nn 的数列,你需要进行下面的询问 nn 次:

  • 询问某区间的最小众数。

对于 100%100\% 的数据,保证 n,m<=100000n,m <= 100000

考虑维护整块答案和连续段整块出现次数,此处可以做到 O(nn)\mathcal O(n \sqrt{n})

考虑散块答案对整块已有答案影响。

时间复杂度 O(nn)\mathcal O(n \sqrt{n})

P4135 作诗

已知一个长为 nn 的数列,你需要进行下面的询问 mm 次:

  • 询问某区间出现过且出现次数为偶数次的数的个数。

对于 100%100\% 的数据,保证 n,m<=100000n,m <= 100000

与上一题相同,考虑维护连续整块答案和散块对于整块已有答案的影响。

这里需要开一个桶,为了保证复杂度在 O(sqrtn)\mathcal O(sqrt{n}),不该使用 memset ,将已有操作撤回即可。

CF1620E Replace the Numbers

你有一个数列,初始为空,你需要进行下面的两种操作 mm 次:

  • 在数列末尾插入一个数 xx
  • 将所有值为 xx 的数改为 yy

操作结束后输出最终数列。

对于 100%100\% 的数据,保证 m<=500000m <= 500000

考虑并查集。

使用一个 valval 表示一个并查集的根所对应的值,使用一个 namename 表示一个值所对应的并查集的根。

改值时,如果值 yy 目前存在,把 name xname \ x(也就是表示值 xx 的并查集所代表的根)合并到 name yname \ y 处,并且清空 name xname \ x

如果值 yy 目前不存在,就把 name yname \ y 修改为 name xname \ x,把 name xname \ x 所对应的 valval 修改为 yy,并且清空 name xname \ x

P4117 [Ynoi2018] 五彩斑斓的世界

已知一个长为 nn 的数列,你需要进行下面的两种操作 mm 次:

  • 把某区间所有大于 xx 的数减去 xx
  • 查询某区间 xx 的出现次数。

对于 100%100\% 的数据,保证 n,m<=1000000ai<=100001n,m <= 1000000,a_i <= 100001

空间限制 64MB

分块照常套路,对于散块暴力减去 xx,下面只考虑整块。

把大于 xx 的数减去 xx ,也就是把所有大于 xx ,小于等于区间最大值 maxxmaxx 的值 pp 更改为 pxp - x

对于每一个整块建立一个并查集,更改值可以参考上一题的做法。

查询出现次数并查集维护一个 cnt xcnt \ x 表示 xx 的出现次数即可。

但是直接更改值复杂度是错的。

考虑 maxx=100001maxx = 100001x=1x = 1 时,需要进行 100000100000 次操作。

发现把所有大于 xx 的值全都减去 xx 等价于把所有小于等于 xx 的数全都加上 xx 并区间减 xx

上面那个例子,如果使用这种修改方法就极其高效。

考虑综合两种方法。

如果 maxx2xmaxx \leq 2 \cdot x,使用第一种方法把大于 xx 小于等于 maxxmaxx 的数 ii 改为 ixi - x

如果 maxx>2xmaxx > 2 \cdot x,使用第二种方法把小于等于 xx 的数 ii 改为 i+xi + x 并打上区间减 xxtag

显然该办法的作用就是将整体值域左右端点拉近来保证复杂度,所以最大总操作数为 nn 次。

该做法空间复杂度为 O(nn\mathcal O(n \sqrt{n},无法通过,逐块处理即可。

posted @ 2022-01-18 07:54  蒟蒻orz  阅读(2)  评论(0编辑  收藏  举报  来源