一维前缀和:
适用题型:对于长度为 n 的序列,进行 m 次询问,每次查询区间 [L, R] 之间的数的和,朴素做法的复杂度为 n * m,而使用前缀和就将复杂度将为了 m 。
已知a[1], a[2], ..., a[i]
则前缀和数组:
S[i] = a[1]+...+a[i]
s[1] = a[1], s[2] = s[1] + a[2], s[i] = s[i-1] + a[i] (规定s[0] = 0)
那么,对于每次询问,s[r] - s[l-1] 就是我们要求的区间和了:
s[r] = a[1]+a[2]+...+a[l-1]+a[l]+a[l+1]+...+a[r];
s[l-1] = a[1]+a[2]+...+a[l-1];
通过一次运算求出任意一段区间内所有数之和,时间复杂度仅为O(1)
s[r] - s[l-1] = a[l]+a[l+1]+...+a[r];
- 预处理前缀和数组
- 用公式求区间和
二维前缀和:
类似与一维前缀和,思维难度稍高一点。主要用在二维矩阵里,时间复杂度会由
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
当要求某一矩形区域的和时,只需要知道在对角线上的两个顶点的 x, y 坐标
假设:左上(x1, y1),右下(x2, y2)
那么区域和计算方法为:
ans = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]
题目描述:
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数n,m,q。 接下来n行,每行包含m个整数,表示整数矩阵。 接下来q行,每行包含四个整数x1, y1, x2,
y2,表示一组询问。
输出格式
共q行,每行输出一个询问的结果。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int MAXN = 1010;
int n, m, q, x1, y1, x2, y2;
LL a[MAXN][MAXN];
LL s[MAXN][MAXN];
int main() {
scanf("%d %d %d", &n, &m, &q);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
scanf("%lld", &a[i][j]);
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
}
}
while(q--) {
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
printf("%lld\n", s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]);
}
return 0;
}
差分:
对于一个给定的数列 A,它的差分数列 B 定义为:
B[1] = A[1], B[i] = A[i] - A[i-1] (2 <= i <= n)
容易发现,“前缀和” 与 “差分” 是一对互逆运算,差分序列 B 的前缀和就是原序列 A,前缀和序列 S 的差分序列也是原序列 A。
给你一串长度为n的数列a1,a2,a3......an,要求对a[L]~a[R]进行m次操作:
操作一:将a[L]~a[R]内的元素都加上c
操作二:将a[L]~a[R]内的元素都减去c
最后再给出一个询问求a[L]-a[R]内的元素之和?
你会怎么做呢?你可能会想,我对于m次操作每次都遍历一遍a[L]~a[R],
给区间里的数都加上c或减去c,最后再求一次前缀和就行了,但这种朴素做法复杂度还是 m * n
对于1<=n,m<=1e5这个数据范围来说直接就GG了
是这个时候我们的差分就该派上用场了,我们新开一个数组b,储存每一次的修改操作
最后求前缀和的时候统计一下就能快速的得到正确答案了,
要对 l 到 r 内的数加 C 时:
b[l] += C, b[r+1] -= C ,就完成了操作
利用差分的概念,就没必要对区间内所有的数进行操作了。
例题: 差分 (模板题 hulu面试题)
题目描述:
输入一个长度为n的整数序列。 接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数n和m。 第二行包含n个整数,表示整数序列。 接下来m行,每行包含三个整数l,r,c,表示一个操作。
输出格式
共一行,包含n个整数,表示最终序列。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n, m, l, r, c;
int a[100010], b[100010];
// 其实只需要一个数组就可以
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++) {
b[i] = a[i] - a[i-1];
}
while(m--) {
scanf("%d %d %d", &l, &r, &c);
b[l] += c, b[r+1] -= c;
}
for(int i = 1; i <= n; i++) {
printf("%d ", b[i] += b[i-1]);
// 注意这里求新数组的方法
}
return 0;
}