前缀和
使用场景
求一个静态数组某个区间内所有数的和 的时候,我们便可以使用前缀和,有效提高运行效率。
前缀和 是非常重要的一个思想,在很多问题中都会用到。
当然,快速地求某一段数的和,也有其它算法可以优化,比如树状数组、线段树。区别在于:
- 前缀和时间复杂度为 O(1),线段树与树状数组都是 O( logn ) 。
- 前缀和只能处理静态数组,这意味着被前缀和的数组的每个元素如果改变,前缀和的结果依旧是旧值的和。但是线段树与树状数组却可以一起动态改变。时间效率更高,往往意味着会损失一些东西。
使用步骤
定义一个前缀和数组 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
}
子矩阵的和(二维数组前缀和)
应用场景
求一个二维矩阵的任意一个矩形区域的和。
算法思路
求二维矩阵的前缀和矩阵(也是一个二维矩阵)。这个前缀和矩阵的 s[i] [j] 表示 原矩阵 从左上角的点a[1] [1] 到 右下角 a[i] [j] 这个矩形范围内所有元素的和。
如何计算前缀和矩阵?
利用容斥原理计算。
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]] + a[i][j]
根据上式,我们可以在线性的时间内,通过原矩阵来算出 二维前缀和矩阵。
如何利用求出的前缀和数组快速算出原矩阵某个矩形区域元素的和?
还是利用容斥原理。
求原矩阵红色矩形区域的和:
s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]
简记:后面带2的变量一定没有 -1,后面带1的变量一定被 -1 。
即:
上式还是只需要常数次便可以计算出来,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);
}
}