分块思想

数据结构 / decompose

本地源文件:docs/ds__decompose.md

分块思想

简介

其实,分块是一种思想,而不是一种数据结构.

从 NOIP 到 NOI 到 IOI,各种难度的分块思想都有出现.

分块的基本思想是,通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度.

分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相应的时间复杂度.

分块是一种很灵活的思想,相较于树状数组和线段树,分块的优点是通用性更好,可以维护很多树状数组和线段树无法维护的信息.

当然,分块的缺点是渐近意义的复杂度,相较于线段树和树状数组不够好.

不过在大多数问题上,分块仍然是解决这些问题的一个不错选择.

下面是几个例子.

区间和

例题 LibreOJ 6280 数列分块入门 4

给定一个长度为 𝑛n 的序列 {𝑎𝑖}{ai},需要执行 𝑛n 次操作.操作分为两种:

  1. 给 𝑎𝑙 ∼𝑎𝑟al∼ar 之间的所有数加上 𝑥x
  2. 求 ∑𝑟𝑖=𝑙𝑎𝑖∑i=lrai

1 ≤𝑛 ≤5 ×1041≤n≤5×104

我们将序列按每 𝑠s 个元素一块进行分块,并记录每块的区间和 𝑏𝑖bi

𝑎1,𝑎2,…,𝑎𝑠⏟⏟𝑏1,𝑎𝑠+1,…,𝑎2𝑠⏟⏟𝑏2,…,𝑎(𝑠−1)×𝑠+1,…,𝑎𝑛⏟__⏟𝑏𝑛𝑠a1,a2,…,as⏟b1,as+1,…,a2s⏟b2,…,a(s−1)×s+1,…,an⏟bns

最后一个块可能是不完整的(因为 𝑛n 很可能不是 𝑠s 的倍数),但是这对于我们的讨论来说并没有太大影响.

首先看查询操作:

  • 若 𝑙l 和 𝑟r 在同一个块内,直接暴力求和即可,因为块长为 𝑠s,因此最坏复杂度为 𝑂(𝑠)O(s)
  • 若 𝑙l 和 𝑟r 不在同一个块内,则答案由三部分组成:以 𝑙l 开头的不完整块,中间几个完整块,以 𝑟r 结尾的不完整块.对于不完整的块,仍然采用上面暴力计算的方法,对于完整块,则直接利用已经求出的 𝑏𝑖bi 求和即可.这种情况下,最坏复杂度为 𝑂(𝑛𝑠 +𝑠)O(ns+s)

接下来是修改操作:

  • 若 𝑙l 和 𝑟r 在同一个块内,直接暴力修改即可,因为块长为 𝑠s,因此最坏复杂度为 𝑂(𝑠)O(s)
  • 若 𝑙l 和 𝑟r 不在同一个块内,则需要修改三部分:以 𝑙l 开头的不完整块,中间几个完整块,以 𝑟r 结尾的不完整块.对于不完整的块,仍然是暴力修改每个元素的值(别忘了更新区间和 𝑏𝑖bi),对于完整块,则直接修改 𝑏𝑖bi 即可.这种情况下,最坏复杂度和仍然为 𝑂(𝑛𝑠 +𝑠)O(ns+s)

利用均值不等式可知,当 𝑛𝑠 =𝑠ns=s,即 𝑠 =√𝑛s=n 时,单次操作的时间复杂度最优,为 𝑂(√𝑛)O(n)

参考代码

---|---

## 区间和 2

上一个做法的复杂度是 Ω(1),𝑂(√𝑛)Ω(1),O(n)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

我们在这里介绍一种 𝑂(√𝑛) −𝑂(1)O(n)−O(1)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的算法.

为了 𝑂(1)O(1)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 询问,我们可以维护各种前缀和.

然而在有修改的情况下,不方便维护,只能维护单个块内的前缀和.

以及整块作为一个单位的前缀和.

每次修改 𝑂(𝑇 +𝑛𝑇)O(T+nT)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

询问:涉及三部分,每部分都可以直接通过前缀和得到,时间复杂度 𝑂(1)O(1)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

## 对询问分块

同样的问题,现在序列长度为 𝑛n![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),有 𝑚m![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个操作.

如果操作数量比较少,我们可以把操作记下来,在询问的时候加上这些操作的影响.

假设最多记录 𝑇T![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个操作,则修改 𝑂(1)O(1)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),询问 𝑂(𝑇)O(T)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

𝑇T![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个操作之后,重新计算前缀和,𝑂(𝑛)O(n)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

总复杂度:𝑂(𝑚𝑇 +𝑛𝑚𝑇)O(mT+nmT)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

𝑇 =√𝑛T=n![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 时,总复杂度 𝑂(𝑚√𝑛)O(mn)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

### 其他问题

分块思想也可以应用于其他整数相关问题:寻找零元素的数量、寻找第一个非零元素、计算满足某个性质的元素个数等等.

还有一些问题可以通过分块来解决,例如维护一组允许添加或删除数字的集合,检查一个数是否属于这个集合,以及查找第 𝑘k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 大的数.要解决这个问题,必须将数字按递增顺序存储,并分割成多个块,每个块中包含 √𝑛n![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个数字.每次添加或删除一个数字时,必须通过在相邻块的边界移动数字来重新分块.

一种很有名的离线算法 [莫队算法](../../misc/mo-algo/),也是基于分块思想实现的.

## 练习题

  * [UVa - 12003 - Array Transformer](https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3154)
  * [UVa - 11990 Dynamic Inversion](https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3141)
  * [SPOJ - Give Away](http://www.spoj.com/problems/GIVEAWAY/)
  * [Codeforces - Till I Collapse](http://codeforces.com/contest/786/problem/C)
  * [Codeforces - Destiny](http://codeforces.com/contest/840/problem/D)
  * [Codeforces - Holes](http://codeforces.com/contest/13/problem/E)
  * [Codeforces - XOR and Favorite Number](https://codeforces.com/problemset/problem/617/E)
  * [Codeforces - Powerful array](http://codeforces.com/problemset/problem/86/D)
  * [SPOJ - DQUERY](https://www.spoj.com/problems/DQUERY)

**本页面主要译自博文[Sqrt-декомпозиция](http://e-maxx.ru/algo/sqrt_decomposition) 与其英文翻译版 [Sqrt Decomposition](https://cp-algorithms.com/data_structures/sqrt_decomposition.html).其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0.**

* * *

>  __本页面最近更新: 2026/1/7 08:56:54,[更新历史](https://github.com/OI-wiki/OI-wiki/commits/master/docs/ds/decompose.md)
>  __发现错误?想一起完善?[在 GitHub 上编辑此页!](https://oi-wiki.org/edit-landing/?ref=/ds/decompose.md "edit.link.title")
>  __本页面贡献者:[Ir1d](https://github.com/Ir1d), [StudyingFather](https://github.com/StudyingFather), [Xeonacid](https://github.com/Xeonacid), [Yanjun-Zhao](https://github.com/Yanjun-Zhao), [c-forrest](https://github.com/c-forrest), [HeRaNO](https://github.com/HeRaNO), [Chrogeek](https://github.com/Chrogeek), [Enter-tainer](https://github.com/Enter-tainer), [gi-b716](https://github.com/gi-b716), [kenlig](https://github.com/kenlig), [ksyx](https://github.com/ksyx), [linghaiyi](https://github.com/linghaiyi), [Tiphereth-A](https://github.com/Tiphereth-A), [yiyangit](https://github.com/yiyangit)
>  __本页面的全部内容在**[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.zh) 和 [SATA](https://github.com/zTrix/sata-license)** 协议之条款下提供,附加条款亦可能应用