在上一次训练题解中我提到了前缀和和二分两种比较基础的算法,今天来巩固一下前缀和和二维前缀和的相关内容。
让我们先来了解前缀和是什么。在一个数组中,我们有时需要取出一段区间内元素的和,我们可以通过循环语句一次一次遍历,但是有没有什么东西不需要如此繁琐地去进行操作呢?由此,我们可以在元素输入时就对其进行预处理,记录下从头元素到当前元素的和,并记录下来。为什么呢?通过前缀和的操作,我们可以很简单地得出后一个前缀和数组中的元素减去前一个元素就是我们输入的当前元素,同样的,当我们要求一个区间[L, R]的区间和时,只需要用sum[R]-sum[L - 1]就可以了。
粘一道模板题:
https://www.luogu.com.cn/problem/P8218https://www.luogu.com.cn/problem/P8218
题目描述
给定 𝑛 个正整数组成的数列 𝑎1,𝑎2,⋯ ,𝑎𝑛 和 𝑚 个区间 [𝑙𝑖,𝑟𝑖],分别求这 𝑚 个区间的区间和。
对于所有测试数据,𝑛,𝑚≤,𝑎𝑖≤
输入格式
第一行,为一个正整数 𝑛 。
第二行,为 𝑛 个正整数 𝑎1,𝑎2,⋯ ,𝑎𝑛
第三行,为一个正整数 𝑚 。
接下来 𝑚 行,每行为两个正整数 𝑙𝑖,𝑟𝑖,满足1≤𝑙𝑖≤𝑟𝑖≤𝑛
输出格式
共 𝑚 行。
第 𝑖 行为第 𝑖 组答案的询问。
输入输出样例
输入 #1
4 4 3 2 1 2 1 4 2 3
输出 #1
10 5
通过前缀和我们可以快速地解决:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, l, r;
int a[N];
int main()
{
cin >> n;
a[0] = 0;
for(int i = 1; i <= n; i ++)
{
int x;
cin >> x;
a[i] = a[i - 1] + x;
}
cin >> m;
while(m --)
{
cin >> l >> r;
cout << a[r] - a[l - 1] << endl;
}
}
如果我们把前缀和放大到二维又该如何实现呢?
https://www.acwing.com/problem/content/798/https://www.acwing.com/problem/content/798/
输入一个 𝑛 行 𝑚 列的整数矩阵,再输入 𝑞 个询问,每个询问包含四个数𝑥1,𝑦1,𝑥2,𝑦2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数𝑛,𝑚,𝑞。
接下来 𝑛 行,每行包含 𝑚 个整数,表示整数矩阵。
接下来 𝑞 行,每行包含四个整数 𝑥1,𝑦1,𝑥2,𝑦2,表示一组询问。
输出格式
共 𝑞 行,每行输出一个询问的结果。
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
先上代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N], s[N][N];
int n, m, q, x1, x2, y1, y2;
int main()
{
cin >> n >> m >> q;
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= m ; j ++)
cin >> a[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] + a[i][j];
while(q --)
{
cin >> x1 >> x2 >> y1 >> y2;
cout << s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << endl;
}
}
我们将前缀和扩展到二维后,我们不难看出,每一次新插入的点位,它的上下左右四个点将整个矩阵分成四个部分,即,s[i - 1][j], s[i][j - 1], s[i - 1][j - 1], 和这个新插入的点,想想我们之前是怎么操作的?这四部分,我们通过s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j]的操作就能更新当前点,大家可以自行画个图推导一下。而输出的过程其实就是取前缀和的逆运算,相信大家也能轻松体会到它的妙处。