目录
一、一维前缀和的定义
对于一个给定的一维数组 arr,它的一维前缀和数组 sum 中 sum[i] 表示从第 0 个元素到第 i 个元素的总和。用公式表示为:。
可以通过递推的方式求出前缀和数组 sum,即:
①
②
二、 一维前缀和的使用
一维前缀和的主要用处:求任意区间的和。
假设:有 m 个查询,每个查询给出 l 和 r(即 left 和 right)。
需求:计算从数组第 l 个元素到第 r 个元素的和。
-
一般思路:遍历 [l, r] 求和,计算时间复杂度是 O(n),那么 m 个查询的时间复杂度就是 O(mn)。
-
使用前缀和:当 l == 0 时,区间和为 sum[r],当 l >= 1 时,区间和为 sum[r] - sum[l - 1]。时间复杂度降低到 O(n + m)。
三、AcWing 795.前缀和
题目描述:
输入一个长度为 n 的整数序列,接下来再输入 m 个询问,每个询问输入一对 l, r。对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
输入格式:
第一行包括两个整数 n 和 m。
第二行包括 n 个整数,表示整数数列。
接下来 m 行,每行包括两个整数 l 和 r,表示一个询问的区间范围。
数据范围:
0 <= l <= r < n,
1 <= n, m <= 100000
-1000 <= 数列中元素的值 <= 1000
输出格式:
共 m 行,每行输出一个询问的结果。
输入样例:
5 3
2 1 3 6 4
0 1
0 2
1 3
输出样例:
3
6
10
代码实现:
#include <stdio.h>
#define N 100000
int arr[N] = { 0 };
int sum[N] = { 0 };
int main()
{
int n = 0, m = 0;
scanf("%d %d", &n, &m);
// 输入元素
int i = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
// 求前缀和数组
sum[0] = arr[0];
for (i = 1; i < n; i++)
{
sum[i] = sum[i - 1] + arr[i];
}
// 询问
while (m--)
{
int l = 0, r = 0;
scanf("%d %d", &l, &r);
int result = (l == 0 ? sum[r] : sum[r] - sum[l - 1]); // 区间和的计算
printf("%d\n", result);
}
return 0;
}
四、二维前缀和的定义
对于给定的一个二维数组 arr,它的二维前缀和数组 sum 中 sum[i][j] 表示以 sum[0][0] 为左上角元素,以 sum[i][j] 为右下角元素的矩形块中所有元素的总和。用公式表达式为:。
和一维前缀和一样,也可以通过递推的方式求出二维前缀和 sum,即:
①
②
③
④
④ 推导过程如下所示:
当把 sum[i - 1][j] 与 sum[i][j - 1] 相加之和,sum[i - 1][j - 1] 被多加了一遍,因此需要减去这一部分。
最后剩下一个元素 arr[i][j],把这个元素再加上即可算出 sum[i][j]。
五、二维前缀和的使用
一维前缀和的主要用处就是求任意区间的区间和,同理,二维前缀和的主要作用就是快速计算区块和。
假设区块左上角下标为 (x1, y1),右下角下标为 (x2, y2),区块的和为 result,计算公式如下:
①
②
③
④
下图给出了计算 (1, 2) 到 (4, 3) 的区块和的示意图:
六、AcWing 796.子矩阵的和
题目描述:
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
输入格式:
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出格式:
共 q 行,每行输出一个询问的结果。
数据范围:
1 <= n,m <= 1000,
1 <= q <= 200000,
0 =< x1 <= x2 < n,
0 <= y1 <= y2 < m
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
0 0 1 1
1 0 2 3
0 2 2 3
输出样例:
17
27
21
代码实现:
#include <stdio.h>
#define N 1000
int arr[N][N] = { 0 };
int sum[N][N] = { 0 };
int main()
{
int n = 0;
int m = 0;
int q = 0;
scanf("%d %d %d", &n, &m, &q);
// 输入元素
int i = 0;
int j = 0;
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
{
scanf("%d", &arr[i][j]);
}
}
// 求二维前缀和
sum[0][0] = arr[0][0];
for (i = 1; i < n; i++)
{
sum[i][0] = sum[i - 1][0] + arr[i][0];
}
for (j = 1; j < m; j++)
{
sum[0][j] = sum[0][j - 1] + arr[0][j];
}
for (i = 1; i < n; i++)
{
for (j = 1; j < m; j++)
{
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + arr[i][j];
}
}
// 询问
while (q--)
{
int x1, y1, x2, y2;
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
int result = 0;
if (x1 == 0 && y1 == 0)
{
result = sum[x2][y2];
}
else if (x1 == 0)
{
result = sum[x2][y2] - sum[x2][y1 - 1];
}
else if (y1 == 0)
{
result = sum[x2][y2] - sum[x1 - 1][y2];
}
else
{
result = sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
}
printf("%d\n", result);
}
return 0;
}