分块思想
一、性质与证明
分块,故名思意,是将一个区间分成几个块,然后对于每个询问,整合一个或多个(甚至全部区间)的信息,但这种分块和整合是有技巧性de,否则很难有效的降低时间复杂度。
先来看一道例题:
老方有一个长度为 $n$ 的序列,被她的学生拜托完成以下三个操作:
- 修改某位置的元素值
- 将一段区间的元素加上或减去一个值
- 求一个区间内的最大最小值
共有 $m$ 个这样的操作,保证 $n, m <= 50000$
考虑分块。
我们考虑将整个序列划分为 $\sqrt n$ 块,因为这样可以使得总查询时间最短。
证明如下:
设这个序列的完整分块数是 $C$ 块,每一个完整个分块都包含 $S$ 个元素,那么显然可能存在一种情况即在区间 $[l, r]$ 的两端包含有不完整的分块。
如图,蓝色部分即为不完整的分块。
可以发现该序列内有 $C$ 个完整分块和两个不完整分块,且这两个不完整分块内元素数量之和小于 $2S$ ,则对于一个序列而言,我们要进行的查询总数的极限为 $C + 2S$ ,则时间复杂度为 $O(C + 2S)$ ,也就相当于 $O(max{ { C, S } } )$.
因此,由于 $C * S + 2 * S >= r - l + 1$ ,则可以近似的看作当且仅当 $S = C$ 时,时间复杂度可以取得最小上界, 此时 $C = S = \sqrt n $ .
二、具体实现
初始化
考虑用 belong[]
来表示每个点与分块之间的关系。
为了便于阅读,除最终整合实现的代码外,其余代码块一律省略头文件。
且在 vscode 中,如出现 "end"不明确
这一类的报错,可以在相应变量名前加 $::$ 即区域标识符。
$Code$
#define MAXN 50000 + 5
int n, a[MAXN];
int S, C = 0, st[MAXN], end[MAXN]; // st[]:每个块的左边界,end[]:每个块的右边界
int belong[MAXN]; // 记录每个元素与所分块的关系
void init() {
S = int(sqrt(double(n)));
for(int i = 1; i <= n; i += S) {
st[++C] = i;
end[C] = min(n, i + S - 1);
}
/*
也可以写成:C = n / S; if(n % S) C++;
*/
for(int i = 1; i <= C; i++) {
for(int j = st[i]; j <= end[i]; j++) {
belong[j] = i;
/*
我们还可以在这里进行其他一系列区间操作:
sum[i] += a[j];
maxx[i] = max(maxx[i], a[j]);
*/
}
}
}
接下来是区间修改和查询操作。似乎每个数据结构都绕不过这几个
修改操作
考虑对于原序列的单点 $x$ 修改除了会影响到 $a[x]$ ,还会影响到所在分块的和、最大值等系列区间信息。实际上很好实现。
inline void updata(int x, int y) {
int home = belong[x];
a[x] += y;
sum[home] += a[y];
return;
}
查询操作
考虑分治,分两种情况考虑:a. [x, y] 在一个块内;b.[x, y]不在一个块内。
a. [x, y] 在一个块内
如图所示,要查询区间[x, y]的信息,非常简单,直接暴力即可。
if(belong[x] == belong[y]) {
int ans = 0;
for(int i = x; i <= y; i++) {
ans += a[i];
}
return ans;
}
b.[x, y]不在一个块内
如图所示,我们发现,区间[x, y]的和即就是 [x, 绿色块1尾] + 蓝色块 + [绿色块2头,y]
。
所以我们可以将绿色部分暴力,蓝色部分直接查询。
if(belong[x] != belong[y]) {
for(int i = x; i <= ::end[belong[x]]; i++) {
ans += a[i];
}
for(int i = belong[x] + 1; i <= belong[y] - 1; i++) {
ans += sum[i];
}
for(int i = st[belong[y]]; i <= y; i++) {
ans += a[i];
}
return ans;
}
这样便可以完美的解决查询问题。
三、相应习题
实际上,这两道题都是板子题,只要大家看懂了上面的知识,这两道题乱杀。
四、后话
老方很开心,因为你们帮她解决了一个大难题.......(逃)
其实分块的时间复杂度为 $O(\sqrt n * m)$ ,慢于树状数组和线段树,同学们可以在掌握分块思想后去学习树状数组和线段树。
注意,分块是种思想!