前缀和学习笔记与总结

发布时间 2023-07-03 20:19:10作者: Mingrui_Yang
前缀和学习笔记与总结

前缀和

一维前缀和

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]
证明过程如下:

\[\begin{aligned} S_r &= a_1 + a_2 + \cdots + a_{l - 1} + a_l + \cdots + a_r \\ S_{l - 1} &= a_1 + a_2 + \cdots + a_{l - 1} \\ \\ S_r &- S_{l - 1} = a_l + \cdots + a_r = \sum_{i = l}^{r}a_i \\ \end{aligned} \]

前缀和处理为 \(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;
}