分块思想
简介¶
其实,分块是一种思想,而不是一种数据结构。
从 NOIP 到 NOI 到 IOI,各种难度的分块思想都有出现。
分块的基本思想是,通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。
分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相应的时间复杂度。
分块是一种很灵活的思想,相较于树状数组和线段树,分块的优点是通用性更好,可以维护很多树状数组和线段树无法维护的信息。
当然,分块的缺点是渐进意义的复杂度,相较于线段树和树状数组不够好。
不过在大多数问题上,分块仍然是解决这些问题的一个不错选择。
下面是几个例子。
区间和¶
例题 LibreOJ 6280 数列分块入门 4
给定一个长度为
- 给
a_l \sim a_r x -
求
\sum_{i=l}^r a_i 1 \leq n \leq 5 \times 10^4
我们将序列按每
最后一个块可能是不完整的(因为
首先看查询操作:
- 若
l r s O(s) - 若
l r l r b_i O(\dfrac{n}{s}+s)
接下来是修改操作:
- 若
l r s O(s) - 若
l r l r b_i b_i O(\dfrac{n}{s}+s)
利用均值不等式可知,当
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #include <cmath>
#include <iostream>
using namespace std;
int id[50005], len;
// id 表示块的编号, len=sqrt(n) , sqrt的时候时间复杂度最优
long long a[50005], b[50005], s[50005];
// a 数组表示数据数组, b 表示区间和, s 表示块长,因为块可能不是完整的所以要开数组
void add(int l, int r, long long x) { //区间加法
int sid = id[l], eid = id[r];
if (sid == eid) { //在一个块中
for (int i = l; i <= r; i++) a[i] += x, s[sid] += x;
return;
}
for (int i = l; id[i] == sid; i++) a[i] += x, s[sid] += x;
for (int i = sid + 1; i < eid; i++)
b[i] += x, s[i] += len * x; //更新区间和数组(完整的块)
for (int i = r; id[i] == eid; i--) a[i] += x, s[eid] += x;
//以上两行不完整的块直接简单求和,就OK
}
long long query(int l, int r, long long p) { //区间查询
int sid = id[l], eid = id[r];
long long ans = 0;
if (sid == eid) { //在一个块里直接暴力求和
for (int i = l; i <= r; i++) ans = (ans + a[i] + b[sid]) % p;
return ans;
}
for (int i = l; id[i] == sid; i++) ans = (ans + a[i] + b[sid]) % p;
for (int i = sid + 1; i < eid; i++) ans = (ans + s[i]) % p;
for (int i = r; id[i] == eid; i--) ans = (ans + a[i] + b[eid]) % p;
//和上面的区间修改是一个道理
return ans;
}
int main() {
int n;
cin >> n;
len = sqrt(n); //均值不等式可知复杂度最优为根号n
for (int i = 1; i <= n; i++) { //题面要求
cin >> a[i];
id[i] = (i - 1) / len + 1;
s[id[i]] += a[i];
}
for (int i = 1; i <= n; i++) {
int op, l, r, c;
cin >> op >> l >> r >> c;
if (op == 0)
add(l, r, c);
else
cout << query(l, r, c + 1) << endl;
}
return 0;
}
/*
https://loj.ac/s/1151495
*/
|
区间和 2¶
上一个做法的复杂度是
我们在这里介绍一种
为了
然而在有修改的情况下,不方便维护,只能维护单个块内的前缀和。
以及整块作为一个单位的前缀和。
每次修改
询问:涉及三部分,每部分都可以直接通过前缀和得到,时间复杂度
对询问分块¶
同样的问题,现在序列长度为
如果操作数量比较少,我们可以把操作记下来,在询问的时候加上这些操作的影响。
假设最多记录
总复杂度:
其他问题¶
分块思想也可以应用于其他整数相关问题:寻找零元素的数量、寻找第一个非零元素、计算满足某个性质的元素个数等等。
还有一些问题可以通过分块来解决,例如维护一组允许添加或删除数字的集合,检查一个数是否属于这个集合,以及查找第
一种很有名的离线算法 莫队算法,也是基于分块思想实现的。
练习题¶
- UVA - 12003 - Array Transformer
- UVA - 11990 Dynamic Inversion
- SPOJ - Give Away
- Codeforces - Till I Collapse
- Codeforces - Destiny
- Codeforces - Holes
- Codeforces - XOR and Favorite Number
- Codeforces - Powerful array
-
本页面主要译自博文 Sqrt-декомпозиция 与其英文翻译版 Sqrt Decomposition。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。
build本页面最近更新:,更新历史
edit发现错误?想一起完善? 在 GitHub 上编辑此页!
people本页面贡献者:Ir1d, HeRaNO, Xeonacid
copyright本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用