前缀和与差分

一.一维前缀和与差分.

什么是前缀和?

我们来考虑这样一个问题:

老方的导师派给老方一个任务:给定一个长度为 n 的序列, 要求完成以下两个操作:

  1. 输出前 k 项的和
  2. 输出区间 [L, r] 的和

我们考虑一下朴素算法。

对于操作1,可以在读入时一并处理,复杂度 O(k);

对于操作2,可以用一个循环遍历数组,复杂度 O(r - L);

然而如果数据范围很大的话,再考虑上读入的时间复杂度,是很不优的。

事实上,这个查询的复杂度是可以优化到 O(1) 的。

用前缀数组 pre[i] 来表示 序列前 i 项和,那么在读入时就可以完成对 pre[] 的初始化。

for(int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
    pre[i] = pre[i - 1] + a[i];
}

当我们查询的时候,可以直接用这个 pre[] 数组,也就是:

scanf("%d%d", &l, &r);
printf("%d%d", pre[r] - pre[l-1]);

这样时间复杂度就很优了。

什么是差分呢?

其实刚刚讲的查询就是差分。

我们可以这样理解,差分就是前缀和的目的,为了能有个好的复杂度,我们通过维护一个前缀和,再用它来实现差分。

实际上,一切具有可减性的操作都可以通过前缀和与差分来实现,然而区间最值这种不具有可减性的就不能用前缀和。

二.二维前缀和与差分

二维前缀和

老方在解决了上一个任务后,又受到另一位导师的委托:

给出一个 n × n 的矩阵,要求完成以下操作:

  1. 输出以(1, 1) 为左下角,(a, b) 为右上角的矩阵中每个元素的和。
  2. 输出以(a, b) 为左下角,(c, d) 为右上角的矩阵中每个元素的和。

在之前一维前缀和的基础上,我们想到,可以预处理一个二维数组 pre[] ,来优化时间复杂度。

如图所示:

红色矩阵的面积 = 黄色矩阵面积 + 绿色矩阵面积 - 蓝色矩阵面积 + 紫色矩阵面积。

因此我们可以这样初始化:

for(int i = 1; i <= n; i++) {
    for(int j = 1; j <= n; j++) {
        scanf("%d", a[i][j]);
        pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + a[i][j];
    }
}

可以结合图片来理解。

差分

这里借用 lxl 上课时 ppt 里的一张图:

由图可得:

scanf("%d%d%d%d", &a, &b, &c, &d);
ans = pre[c][d] - pre[a][b] - pre[a-1][d] - pre[c][b-1] + pre[a-1][b-1];
printf("%d", ans);

三.例题

Luogu1719 最大加权矩形

这道题几乎就是上面前缀和知识的直接体现。

看一眼数据范围,可以暴力枚举这个矩阵中的所有子矩阵,时间复杂度是 O(n^4) ,可以通过本题。