分块

我要陪你 走下去 一直到无边天际

感谢我们无数次交付彼此的勇气

——洛天依《珍珠》


分块

总结:多写,多练。

算法思想本身很简单,就看维护什么和怎么维护了。

如果不是一眼的分块,当遇到存在两个暴力和两个限制时间复杂度的东西,且两个暴力的复杂度正好互补的时候,可以考虑分块。

一个突破点是从散块入手。预处理整块的完整答案,在把散块中涉及到的整块的信息统计出来,这样只需要 \(O(\sqrt n)\) 的复杂度,避免了直接统计整块。

当然还有一个重要的思想,就是操作分块。

天依宝宝可爱!


LOJ 6277 | 数列分块

思维难度:\(\color{#F39C11} 橙\) *900

先是 LOJ 上的数列分块入门 \(9\)

第一题,很简单的。

分为 \(B = \sqrt n\) 个块,每个块维护一个加法 tag,修改时要特判 \(l,r\) 在同一个块的情况。

简化的技巧是预处理每个 \(i\) 在哪个块与每个块的左右端点,这样在操作的时候会更好写一些。

submission

天依宝宝可爱!


LOJ 6278

思维难度:\(\color{#FFC116} 黄\) *1300

第二题,还是很简单的。

因为要求的是小于 \(k\) 的数字个数,显然不好用一个数组直接维护。

注意到静态求这个东西最简单的方法是排序 + 二分,所以考虑维护每个整块内的有序性,那么先对每个整块进行排序,然后对于操作:

  • 修改操作:散块部分直接暴力修改,然后再对这个散块对应的整块重新排序,复杂度 \(O(B \log B)\);对于整块部分,可以发现整体 \(+k\) 不影响相对大小,所以直接打个 tag 就可以了,复杂度 \(O(\frac n B)\)

  • 查询操作:散块部分依旧是暴力,复杂度 \(O(B)\);整块部分因为已经是有序的了,所以直接二分即可,复杂度 \(O(\frac n B \log B)\)

所以总复杂度为 \(O(n (B \log B + \frac n B + B + \frac n B \log B)) = O(n \log B (B + \frac n B))\),显然 \(B = \sqrt n\) 时最优。

注意该加上 tag 的时候要加上 tag。

submission

天依宝宝可爱!


LOJ 6297

思维难度:\(\color{#FFC116} 黄\) *1300

和上一题几乎完全一样的。

还是注意在该加 tag 的地方加 tag。

submission

天依宝宝可爱!


LOJ 6280

思维难度:\(\color{#F39C11} 橙\) *900

和第一题一样,只不过是区间修改罢了,简单。

注意不要把 ibl[i] 写混。

submission

天依宝宝可爱!


LOJ 6281

思维难度:\(\color{#FFC116} 黄\) *1400

注意到对于一个数开根衰减的很快,是 \(\log \log V\) 的复杂度,所以考虑当快内最大值不是 \(1\) 时暴力进行一次开根,因为 \(V = 2^{31} - 1\),所以一个块最多开 \(6\) 次根。

关于块长,先分析复杂度,因为修改的复杂度类似于是均摊的,所以分析复杂度。查询的总复杂度显然为 \(O(q \times (B + \frac n B))\);修改的总复杂度为 \(O(q \log \log V \times B)\)(整块散块相同)。可以推出 \(B = \sqrt {n \log \log V}\) 时最优。

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

submission

天依宝宝可爱!


LOJ 6282

思维难度:\(\color{#FFC116} 黄\) *1400

单点插入,单点查询,看起来非常非常不好维护的操作。

不过考虑分块,类似 trie 查排名的操作,从前往后用 \(O(\frac n B)\) 的复杂度找到插入或查询的元素所在的块,然后用 \(O(B)\) 的复杂度在块内插入(可以用 vector::insert)。

但是可以发现,当一个块内堆了很多元素,可以达到 \(O(q)\) 量级的时候,复杂度会退化为 \(O(q^2)\)

似乎不好解决。但考虑最简单最暴力(分块的核心就是一种暴力呐)的解决方案,直接重构整个序列的分块。

那么这时候就要选一个阈值 \(T\),如果块长 \(\ge B + T\) 就重构。这时候操作的总复杂度即为 \(O(q \times (\frac n B + B + T))\),重构复杂度为 \(O(n \times \frac q T)\),可以计算出当 \(B = T = \sqrt n\) 是最优。

复杂度依然是 \(O(q \sqrt n)\)

注意是 \(\ge B + T\) 而不是 \(\ge T\)

submission

天依宝宝可爱!


LOJ 6283

思维难度:\(\color{#F39C11} 橙\) *1000

和线段树 2 一样的,注意 tag 的维护顺序。

把单点查询写成区间查询 😃

submission

天依宝宝可爱!


LOJ 6284

思维难度:\(\color{#FFC116} 黄\) *1400

是 ODT 模版题,但是这是分块的题单,所以要用分块做。

(后面会补 ODT 的qwq

和 #5 有相似之处,因为整块推平之后就可以 \(O(1)\) 查询了,推平也是 \(O(1)\) 的。所以对于未推平的块用 map(实测 map < umap)统计,推平的直接打个 tag 即可。

在整块重置的时候,推平 tag 记得下传。

submission

天依宝宝可爱!


LOJ 6285

思维难度:\(\color{#52C41A} 绿\) *1700

静态区间众数,双倍经验:P4168(那个被离散化艹飞的分块)

需要一点注意力,注意到如果要求 \([l,r+x]\) 的众数,那么它一定是 \([l,r]\) 的众数或者 \((r,r+x]\) 中的一个数。因为 \([l,r]\) 中的没有出现在 \((r,r+x]\) 中的数一定比不过 \([l,r]\) 的众数。

可以发现这个东西给了我们一点启示,就是可以用某种手段快速求出一个大块 \([l,r]\) 的众数 和 小块 \((r,r+x]\) 中的每个数在 \([l,r]\) 中的出现次数。

所以想到分块,分为 \(\frac n B\) 个块,因为是静态的,所以考虑预处理出每两个之间的众数(包括 \(l_块 \sim r_块\) 之间所有的块),一共 \((\frac n B)^2\) 对;查询中的整块就解决了,小块单独算即可。

小块部分可以 map 或二分之类的 log 做法,但也可以预处理每个数出现次数的前缀,即 \(s_{i,j}\) 表示数 \(i\) 在块 \(1 \sim j\) 的出现次数。这样就可以做到每个数 \(O(1)\) 判断了。预处理众数部分也可以类似地操作,以每个块为左端点扫一遍就可以了。

submission

天依宝宝可爱!


洛谷 P11527

思维难度:\(\color{#52C41A} 绿\) *1900

有效操作的数量非常有限时,分块是一个很好的选择。

注意到当 \(a_i\) 的质因数足够丰富的时候,\(\gcd(i,a_i) = i\),所以考虑记录每个 \(i\) 剩余的(即 \(a_i\) 没有的)因数,顺便可以维护 gcd。注意到 \(c_i\)\(n\) 同阶,所以可以维护一个 tag \(f_{i,j}\) 表示块 \(i\) 内的第 \(j\) 个质因数是否还有剩余。

每次修改散块暴力,整块看看 \(k\) 的质因数在块 \(i\) 有没有剩余,如果有就直接重构,没有就打上乘法 tag。

关于查询,显然可以维护 \(nxt_i\) 表示 \(i\) 往后延伸到的第一个块外的位置,\(s_i\) 表示 \(i\) 在块内延伸到的所有数之和,显然后缀扫一遍就可以算出来。

复杂度:

  • 修改
    • 散块显然是 \(O(q \times B)\)
    • 整块直观感觉均摊是一只 log 的,不过实际上根据 这篇题解,是 ln ln 的,不过差别不大。复杂度 \(O(B \times n \ln \ln n)\)
  • 查询显然是 \(O(q \times \frac n B)\)

所以 \(B = \sqrt {n \ln \ln n}\) 最优,不过不卡常,\(\sqrt n\) 也能过。

调了 3days 的死因:

  • 数组开小。
  • 散块操作的时候,\(f_{i,j}\) 应该在整个块判断,而不是只看被操作的部分(这个是真的【】,拿题解拍了好久才拍出来)。

submission

天依宝宝可爱!


CF342E | 操作分块

思维难度:\(\color{#FFC116} 黄\) *1500

竟然可以对操作序列分块……

先看序列分块,就是把序列分成一块一块的之后,可以把修改和询问看成是在一个区间内的操作,这个区间可以分成两个散块和一些整块,且对这两部分都是好操作的。

但是操作分块,则是对时间轴进行分块,但核心思想和序列分块不同。将时间轴分块后,同一个块内的操作(修改 + 询问)可以一起进行,且对于之前的块的结果是好操作的。那么对于每个询问,只需要处理「同块内该询问之前的修改」与「之前所有块的操作」这两个东西即可。

有了这个思路,这个题就简单了。

考虑当前处理到了第 \(i\) 个块,对于块内的一个询问 \(q_j\)

  • 首先在 \(i\) 之前的块中的修改,显然可以在每个整块处理完之后进行一次多源 bfs 处理出每个点的最小答案。
  • 对于在块 \(i\) 中的修改,可以直接求 \(j\)\(j\) 之前每个修改点的 LCA 来得到。

有一个好玩的点,就是操作分块实际上是一个在线算法,因为虽然分块,但还是从前往后处理的,无论是块间还是块内。

复杂度:

  • 整块:显然 \(O(n)\)
  • 散块:考虑一个块中的最多 \(B\) 个询问,每个都需要跟最多 \(B\) 个点求 LCA(假设复杂度是 \(\log n\) 的),那么复杂度为 \(O(B \cdot B \log n)\)

所以总复杂度 \(O(n \cdot \frac m B + B^2 \log n \cdot \frac m B)\),所以 \(B = \sqrt {n \log n}\) 时最优。

submission

天依宝宝可爱!

posted @ 2025-08-19 09:38  little__bug  阅读(7)  评论(0)    收藏  举报