前缀和
一维前缀和
What
现有 原数组:
\[a_1,a_2,a_3,\ldots,a_n \]对应的 前缀和数组 应满足:
\[S_i = a_1+a_2+a_3+\cdots+a_n \]前缀和 \(S_i\) 即为 原数组 中前 \(i\) 个数的和
如何求
s[0] = 0;
for (int i = 1; i <= n; i ++ )
s[i] = s[i - 1] + a[i];
从 i = 1
开始 和 定义 s[0] = 0
,是为了解决边界问题。
对于求区间 \(\left[ l, r \right]\) 的和,可通过 s[r] - s[l - 1]
。
证明过程如下:
前缀和处理为 \(O(N)\),查询为 \(O(1)\)。
作用
快速地求出原数组中 连续 一段数的 和
(由 \(O(n)\) 优化到 \(O(1)\))
公式
\[\begin{aligned} &S_r = a_1 + a_2 + \cdots + a_{l - 1} + a_l + \cdots + a_r \\ &S_r - S_{l - 1} = a_l + \cdots + a_r \end{aligned} \]模板
S[i] = a[1] + a[2] + ... a[i];
a[l] + ... + a[r] = S[r] - S[l - 1];
模板题目
AcWing 795. 前缀和 题目入口
题目大意
输入一个长度为 \(n\) 的整数序列。
接下来再输入 \(m\) 个询问,每个询问输入一对 \(l,r\)。
对于每个询问,输出原序列中从第 \(l\) 个数到第 \(r\) 个数的和。
CODE
点击查看代码
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], s[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ )
s[i] = s[i - 1] + a[i]; // 前缀和的初始化
while (m -- )
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", s[r] - s[l - 1]); // 区间和的计算
}
return 0;
}
二维前缀和
What
现有一个矩阵
如图
阴影部分的和表示 \(S_{4,3}\),即 左上端点为 \((1,1)\),右下端点为 \((4,3)\) 的矩阵 中数的总和
所以 \(S_{i,j}\) 就是下图中的阴影部分的总和
\(S_{i,j}\) 怎么算
同样,我们也采用递推的方式,用前面已经求出的 \(S\) 来求 \(S_{i,j}\)。
对于 原数组 \(A\),前缀和数组 \(S\),如下图
\(S_{i-1,j}\) 与 \(S_{i,j-1}\) 相加将多出一个 \(S_{i-1,j-1}\)(即紫色重叠部分),所以需要减去一个 \(S_{i-1,j-1}\).
在加上 \((i,j)\) 本身,即加上 \(A_{i,j}\).
所以
\[S_{i,j}=S_{i-1,j}+S_{i,j-1}-S_{i-1,j-1}+A_{i,j} \]for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &s[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
矩阵的和
对于 左上端点为 \((x_1,y_1)\),右下端点为 \((x_2,y_2)\) 的矩阵
如图
用 \(S_{x_2,y_2}\) 减去 \(S_{x_2,y_1-1}\) 与 \(S_{x_1-1,y_2}\),\(S_{x_1-1,y_1-1}\)(紫色部分)被多减了一次。
所以
\[Sum = S_{x_2,y_2}-S_{x_2,y_1-1}-S_{x_1-1,y_2}+S_{x_1-1,y_1-1} \]Sum = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
公式
\[S_{i,j}=S_{i-1,j}+S_{i,j-1}-S_{i-1,j-1}+A_{i,j} \]\[Sum = S_{x_2,y_2}-S_{x_2,y_1-1}-S_{x_1-1,y_2}+S_{x_1-1,y_1-1} \]模板
S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &s[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
Sum = S_{x_2,y_2}-S_{x_2,y_1-1}-S_{x_1-1,y_2}+S_{x_1-1,y_1-1}
模板题目
AcWing 796. 子矩阵的和 题目入口
题目大意
输入一个 \(n\) 行 \(m\) 列的整数矩阵,再输入 \(q\) 个询问,每个询问包含四个整数 \(x1,y1,x2,y2\),表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
CODE
点击查看代码
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int s[N][N];
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", &s[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
while (q -- )
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
}
return 0;
}