前缀和

前缀和

使用场景

求一个静态数组某个区间内所有数的和 的时候,我们便可以使用前缀和,有效提高运行效率。

前缀和 是非常重要的一个思想,在很多问题中都会用到。

当然,快速地求某一段数的和,也有其它算法可以优化,比如树状数组、线段树。区别在于:

  1. 前缀和时间复杂度为 O(1),线段树与树状数组都是 O( logn ) 。
  2. 前缀和只能处理静态数组,这意味着被前缀和的数组的每个元素如果改变,前缀和的结果依旧是旧值的和。但是线段树与树状数组却可以一起动态改变。时间效率更高,往往意味着会损失一些东西。

使用步骤

定义一个前缀和数组 s,其中,s[i] = a[`1] + a[2] + … + a[i]。特殊规定,s[0] = 0。

根据上面的定义,我们发现处理 s[i] 效率可以很高,因为 s[i] = s[i - 1] + a[i] 。一个 for 循环即可得到前缀和数组。

因此,利用前缀和我们可以只需 O(1) 的复杂度就可以算出任意区间 [L, R] 内的 数组 a 的和。利用 s[R] - S[L - 1] 即可算出区间和(从 O(n) 优化到了 O(1))。

前缀和 代码

for (int i = 1; i <= n; ++i) { // 注意 i 是从 1 开始的
    scanf("%d", &a[i]);
    s[i] = s[i - 1] + a[i]; // 前缀和 s[0] 规定为 0
}

子矩阵的和(二维数组前缀和)

应用场景

求一个二维矩阵的任意一个矩形区域的和。

image-20200920160209463

算法思路

求二维矩阵的前缀和矩阵(也是一个二维矩阵)。这个前缀和矩阵的 s[i] [j] 表示 原矩阵 从左上角的点a[1] [1] 到 右下角 a[i] [j] 这个矩形范围内所有元素的和。

image-20200920160513927

如何计算前缀和矩阵?

利用容斥原理计算。

s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]] + a[i][j]

根据上式,我们可以在线性的时间内,通过原矩阵来算出 二维前缀和矩阵。

如何利用求出的前缀和数组快速算出原矩阵某个矩形区域元素的和?

还是利用容斥原理。

image-20200920161459789

求原矩阵红色矩形区域的和:

s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]

简记:后面带2的变量一定没有 -1,后面带1的变量一定被 -1 。

即:image-20200920162100893

上式还是只需要常数次便可以计算出来,O( 1 )。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int a[N][N], s[N][N];
int n, m, q;

int main()
{
    scanf("%d%d%d", &n, &m, &q);

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

    int x1, y1, x2, y2;
    while (q--) {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);

        int result = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
        printf("%d\n", result);
    }
}

参考:ACWing-差分与前缀和

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页