分块常用来解决一些区间问题,例如区间加法,区间求和,区间最小值等等
其主要思想是将长度为 n 的数据分为若干块,处理每一块上的信息,在查询的时候完成快速的查询。
我们采用分块的思想,我们将方格分为 B 组,每组即为 nB 个元素,如下所示:
如上,每组元素我们划分五个,我们将 n 个元素分为了 B=⌈n5⌉ 组,同时可能不满足每组都有五个元素,最后一组空缺一些元素,但是无关紧要。
在分完组后,我们维护每组的一个和值,我们用 sum[i] 表示第 i 组的和值,对于每个元素,如果他被修改了,那么我们找到对应的组 x,对 sum[x] 进行相应的操作即可。
如下图:
更新十分的简单。
那么如何进行查询呢,比如我要查询某个区间 [a,b]的和。
那么就分为两种情况:
- 询问的左右端点在同一个组,即如下情况:
那么我们可以直接进行区间内循环,即暴力循环。
- 询问的左右端点不在同一个组,即如下:
那么对应查询区间内完整的组,我们直接查询其 sum 即可,对于不完整的组,我们暴力循环区间,例如在上图中,在第一组的区间内,我们暴力循环 [3,5] 即可。
我们分析一下复杂度:
- 我们分的组数量为 B。
- 那么每次查找最多包含的组数为 ,也就是说,对于完整的组,可能最多循环 次。
- 对于不完整的组,每次查询暴力循环可能需要 次。
那么对于一次查询的复杂度为,为了使得复杂度最小,我们用不等式可以算出 是最合适的。
static int[] bL = new int[N];
static int[] bR = new int[N];
static int[] gid = new int[N];
static int[] a = new int[N];
static int[] sum = new int[N];
static int gnum, Bnum;
public static void initBlock(int n) {
gnum = 0;
Bnum = (int) Math.sqrt(n) + 1;//获得块数
for (int i = 1; i <= n; i += Bnum) {
//通过遍历,确定每一个块的起始位置
//为什么要加一:int取整仅仅取整,不会四舍五入,加一防止溢出
gnum++;
bL[gnum] = i;
bR[gnum] = Math.min(n, i + Bnum - 1);
//对于块内的每一个序号,确定所在的块号,以及加和
for (int j = bL[gnum]; j <= bR[gnum]; j++) {
gid[j] = gnum;
sum[gnum] += a[j];
}
}
}
浙公网安备 33010602011771号