分块常用来解决一些区间问题,例如区间加法,区间求和,区间最小值等等

其主要思想是将长度为 n 的数据分为若干块,处理每一块上的信息,在查询的时候完成快速的查询。

我们采用分块的思想,我们将方格分为 B 组,每组即为 nB 个元素,如下所示:

图片描述

如上,每组元素我们划分五个,我们将 n 个元素分为了 B=⌈n5⌉ 组,同时可能不满足每组都有五个元素,最后一组空缺一些元素,但是无关紧要。

在分完组后,我们维护每组的一个和值,我们用 sum[i] 表示第 i 组的和值,对于每个元素,如果他被修改了,那么我们找到对应的组 x,对 sum[x] 进行相应的操作即可。

如下图:

图片描述

更新十分的简单。

那么如何进行查询呢,比如我要查询某个区间 [a,b]的和。

那么就分为两种情况:

  1. 询问的左右端点在同一个组,即如下情况:

图片描述

那么我们可以直接进行区间内循环,即暴力循环。

  1. 询问的左右端点不在同一个组,即如下:

图片描述

那么对应查询区间内完整的组,我们直接查询其 sum 即可,对于不完整的组,我们暴力循环区间,例如在上图中,在第一组的区间内,我们暴力循环 [3,5] 即可。

我们分析一下复杂度:

  1. 我们分的组数量为 B
  2. 那么每次查找最多包含的组数为 ,也就是说,对于完整的组,可能最多循环  次。
  3. 对于不完整的组,每次查询暴力循环可能需要  次。

那么对于一次查询的复杂度为,为了使得复杂度最小,我们用不等式可以算出  是最合适的。

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];
            }
        }
    }

 

posted on 2025-06-05 09:55  fafrkvit  阅读(27)  评论(0)    收藏  举报