前缀和和差分是一对逆运算,先给出各自最大的作用:
- 前缀和
快速求出原数组中一段数的和(区间和) - 差分
快速对原数组进行统一的操作(eg.某段区间全部+1)
1 前缀和
1.1 是什么
假设有a1, a2, … an,那么前缀和数组Si = a1 + a2 + … + ai,其中S0 = 0,作为边界值。
1.2 作用
前缀和最大的作用是快速求出原数组中一段数的和
- 预处理:O(n)
- 每次询问:O(1)
当用O(n)的时间求出前缀和数组后,每次区间和计算都可以用O(1)的时间复杂度求出。
eg.求出[l ~ r]的区间和:S[r] - S[l - 1]
其中:S[r] = a[1] + a[2] + … + a[l - 1] + a[l] + … + a[r]
S[l - 1] = a[1] + a[2] + … + a[l - 1]
而没有前缀和数组时,虽然不用初始化了,但是每次区间和计算都需要O(n),多次区间和计算会大大增加时间复杂度。
1.3 应用
1.3.1 一维前缀和
- 求前缀和
for(int i = 1; i <= n; i ++) S[i] = S[i - 1] + a[i]; //从1开始
- 求区间和
S[r] - S[l - 1]
1.3.2 二维前缀和
- 求前缀和
随手画了一个图
想算出上图4 × 4的矩阵和,步骤就是:
红色矩阵和 + 蓝色矩阵和 - 绿色矩阵和 + 黄色矩阵和
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];
- 求子矩阵和
想求出绿色(右下角)子矩阵的和,步骤就是:
红色矩阵和 - 橙色矩阵和 - 蓝色矩阵和 + 紫色矩阵和
假设红色矩阵的左上角和右下角坐标分别是(x1, y1), (x2, y2),那么公式为:
S[x2][y2] - S[x1 - 1][y2] - S[x2][y1 - 1] + S[x1 - 1][y1 - 1]
2 差分
2.1 是什么
前面已经提到,差分是前缀和的逆运算。也就是说,给定a1, a2, … an, 构造数组b1, b2, … bn, 使得ai = b1 + b2 + … + bi,数组B就是数组A的差分。(当然,数组A是数组B的前缀和)
2.2 作用
差分最大的作用是快速对原数组进行统一的操作(eg.某段区间全部+1)
- 求差分数组:O(n)
- 每次操作:O(1)
当用O(n)的时间复杂度求出差分数组B后,每次的区间操作都可以用O(1)的时间复杂度实现。
eg.[l ~ r]区间都加1,我们只需要对B数组:b[l] + c
,b[r + 1] - c
当b[l] + c后,对应的前缀和数组A里面的a[l]及之后的都会全+c,再让b[r+1] - c,就可以把不用加的那一段再减回来,保证只在[l, r]区间内操作。
如果不用差分数组的话,每次在原数组上的操作时间复杂度都是O(n),多次的O(n)操作也会大大增加时间复杂度。
2.3 应用
2.3.1 一维差分
- insert函数
void insert(int l, int r, int c){
//使a[l ~ r]内全部+c
b[l] += c;
b[r + 1] -= c;
}
- 求差分数组
实际上,一维差分数组b1 = a1, b2 = a2 - a1, b3 = a3 - a2, … b[n] = a[n] - a[n-1],但我们不从这个结果入手,而是从本质去构造差分数组。
求差分数组有一个很优雅的方法。前缀和数组里每次读入一个ai,在差分数组里都可以理解为对应位置加上ai,而其他位置不做改变,这样就能保证读完一遍ai,就会有对应的差分数组。
//主程序
for(int i = 1; i <= n; i ++){
scanf("%d", &a[i]);
insert(i, i, a[i]);
}
- 每次操作
调用insert函数即可
2.3.2 二维差分
- insert函数
对红色框做统一的操作,步骤是:
紫色框 - 绿色框 - 蓝色框 + 橙色框
(比如紫色框,对左上方x1y1的处理影响的就是其右下部分全部)
void insert(int x1, int y1, int x2, int y2, int c){
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2 + 1][y2 + 1] += c;
}
- 求差分数组
二维和一维差分数组的构造方式一样,怎样由每次的a[i]得到b[i]呢?
一维差分的处理方式是给每个b[i ~ i]区间加一个值a[i],二维差分的处理方式就是给每个b[ij ~ ij]子矩阵加上a[i]就好了。
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
insert(i, j, i, j, a[i][j]);
- 每次操作
调用insert函数即可
注:
在每次差分处理后,题目一定不会要求给出差分数组,而是处理好的数组,我们对差分数组再构造一次前缀和就行,该一维一维,该二维二维。有谁能想到我的二维构造一开始WA就因为单纯+a[i][j]呢
例题
AcWing 795.前缀和
#include<iostream>
using namespace std;
const int N = 100010;
int a[N], S[N];
int main(){
int n, m;
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;
}
AcWing 796. 子矩阵的和
#include<iostream>
using namespace std;
const int N = 1010;
const int M = 1010;
int a[N][M], S[N][M];
int main(){
int n, m, q;
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
scanf("%d", &a[i][j]);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
S[i][j] = S[i][j - 1] + S[i - 1][j] - S[i - 1][j - 1] + a[i][j];
while(q --){
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 -1][y1 - 1]);
}
return 0;
}
AcWing 797. 差分
#include<iostream>
using namespace std;
const int N = 100010;
int a[N], b[N];
void insert(int l, int r, int c){
//使a[l ~ r]内全部+c
b[l] += c;
b[r+1] -= c;
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
//差分初始化
for(int i = 1; i <= n; i ++) insert(i, i, a[i]);
while(m --){
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
//用差分构造前缀和
for(int i = 1; i <= n; i ++){
a[i] = a[i - 1] + b[i];
printf("%d ", a[i]);
}
return 0;
}
AcWing 798. 差分矩阵
#include<iostream>
using namespace std;
const int N = 1010;
const int M = 1010;
int a[N][M], b[N][M];
void insert(int x1, int y1, int x2, int y2, int c){
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main(){
int n, m, q;
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
scanf("%d", &a[i][j]);
//差分初始化
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
insert(i, j, i, j, a[i][j]);
//利用差分处理
while(q --){
int x1, y1, x2, y2, c;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, y2, c);
}
//差分转前缀和
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i -1][j - 1] + b[i][j];
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
bug汇总:
1.Segmentation Fault
scanf没有加&
2.WA
二维差分变二维前缀和没反应过来用公式…