前言
什么是差分和前缀和
有一个
数组a
,数组a中的元素为( a[1] , a[2] ,a[3] ... a[n])
,数组下标从1开始a[0] = 0
有一个数组b
,数组b中的元素为(a[1] - a[0] , a[2] - a[1] , a[3] - a[2] ... a[n] - a[n-1])
此时
b
是a
的差分
,a
是b
的前缀和
总结:
分差:就是该项与前一项的差。例:b[i] = a[i] - a[i-1]
前缀和:就是前n项和 例:a[i] = b[i] + b[i-1] + ... + b[1]
一、前缀和的应用
1.一维前缀和
例题:
有个长度为n
数组输入l
和r
,计算数组第l
个数到第r
个数的和 (l
和r
是q
次输入)
首先想到的就是for暴力出来,每输入一次l
,r
就循环数组求从l
到r
之间的和,但q次输入时的复杂度就是O(n²),这里的前缀和可以把复杂度降到O(n)。
算法思路
1.用数组
s
来存序列的前n项和。
for (int i=1; i<=n; i++) s[i] = s[i-1] + a[i];
2.询问l,r区间和用r的前n项和减去l的前n项和。
printf("%d\n",s[r]-s[l-1]);
总代码如下
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e5 + 10;
int n,m,l,r,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]; //序列的前n项和
while (m--) //复杂度O(n)
{
scanf("%d %d",&l,&r);
printf("%d\n",s[r]-s[l-1]); //r的前n项和减去l的前n项和
}
}
2.前缀和矩阵(二维)
例题
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出格式
共 q 行,每行输出一个询问的结果。
数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例:
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
解题思路
1.用二维数组
a
来存放矩阵中的数值
(下标要从1开始),把矩阵的数值看成
一个小矩形面积
。
2.用二维数组s的每个位置来存该位置到(1,1)点所组成的矩形的面积之和。
a数组的存放如下图
s数组的存放方法:
例如:存放(2,2)位置:(1,1)与(2,2)所组成矩形的面积和
各矩形面积
橙色矩形:面积为4。
绿色矩形:面积为8。
蓝色矩形:面积为6。
绿色和橙色重叠矩形:面积为1。
(2,2)点的面积大小为:橙色矩形 + 绿色矩形 + 蓝色矩形 - 绿色和橙色重叠矩形
代码实现:
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] + a[i][j] - s[i - 1][j - 1];
}
s数组最终的存放如下图
输出所组成的矩形面积:
例如:(2,2)与 (3,4)所组成的矩形面积为
如下图:
各矩形面积
橙色矩形:为所求矩形。
蓝色矩形:面积为14。
绿色矩形:面积为6。
蓝色与绿色重叠的矩形:面积为1。
总矩形:面积为41。
可得黄矩形为:橙色矩形 = 总矩形 – 绿色矩形 – 蓝色矩形 + 蓝色与绿色重叠的矩形。
(x1,y1) ->(2,2)
(x2,y2) ->(3,4)
代码实现:
s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]
总代码如下
#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int n,m,q;
int a[N][N],s[N][N];
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][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j];
}
while (q--)
{
int x1,y1,x2,y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] << endl;
}
}
一、差分的应用
1.一维差分
例题:
有个一长度
为 n
的数组a
。
输入三个整数 l,r,c,表示将数组中 [l,r] 之间
的每个数加上 c
。
请你输出进行完所有操作后的数组。
a[6] = {0,2 2 1 2 1}; //长度为5的数组 2 2 1 2 1
int l = 1,r = 5,c = 1; //下标从1开始处理
a[6] = {0,3,3,2,3,2}; //处理后数组变为 3 3 2 3 2
首先想的方法是用for暴力出来但当n次输入l,r,c时的时间复杂度就是O(n²)。
我们用差分处理:
首先想怎么用数组
b
来还原数组a
for (int i=1; i<=n; i++)
a[i] = b[i] + a[i-1];
如果给数组
b
第l
个位置加上c
,那么在求数组a
时,数组a
比原来在l
位置以后都会加c
。
a[1] = 2;
a[2] = 0;
c = 1;
b[1] = b[1] + c = 3;
b[2] = a[2] - a[1] = 0;
a[1] = b[1] + a[0] = 3;// 2 + 0 = 3
a[2] = b[2] + a[1] = 3;// 0 + 3 = 3
理解:
a[i] = b[i] + a[i-1]
求a[i]
时都会加上前一项a[i-1]
,前一项加c
那么后一项也会加c
。
数列理解:
求前n项和有一项加上了c,那么这项后面的所有项都会比原来多加个c。
但题意是加到r
停止
如果给数组
b
第r +1
个位置减去c
,那么在求数组a
时,数组a
比原来在l+1
位置以后都会减c
。
理解:
a[i] = b[i] + a[i-1]
求a[i]
时都会加上前一项a[i-1]
,前一项减c
那么后一项也会减c
。
数列理解:
求前n项和有一项减去了c,那么这项后面的所有项都会比原来减个c。
图解如下:
红色:
+ c
,绿色:- c
,r+1
后相互抵消达到l
到r
都加c
代码实现如下:
for (int i=1; i<=n; i++)
b[i] = a[i] - a[i-1]; //a数组的分差
while (m--)
{
int l,r,c;
scanf("%d %d %d",&l,&r,&c);
b[l] += c;
b[r+1] -= c;
} //复杂度为O(n)
for (int i=1; i<=n; i++)
a[i] = b[i] + a[i-1]; //用b数组求a数组 l位置开始都加c到r结束
2.差分矩阵(二维)
例题:
输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上 c。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
解题思路
前缀和矩阵的逆运算,现在是已知前缀和数组
a
求差分数组b
。
想一想在前缀和矩阵时是怎么求前缀和的。
代码实现
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
a[i][j] = a[i][j - 1] + a[i - 1][j] - a[i - 1][j - 1] + b[i][j]; //a是前缀和
}
而求差分就是前缀和的逆运算。
代码实现
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
{
b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]; //b是差分b的前缀和就是a
}
}
(x1,y1) 对应(1,1)—— (x2,y2) 对应 (2,2)
如果我们想给橙色矩形都加上
c
操作为:
b[x1][y1] += c;
但是此操作后求前缀和时会对(1,1)到 (3,4)的矩形的都加上c
。
如果对蓝色矩形减
c
,那么从(3,1)到(3,4)求前缀和的矩形都会减c
。
如果对绿色矩形减c
,那么从(1,3)到(3,4)求前缀和的矩形都会减c
。
此时求前缀和只有橙色位置加上了c
。
但是红色矩形被减去了两个c
,红色矩形与(3,3)有关,因此只要给(3,3)点加上c
那么(3,4)在求前缀和时也会被加上c
。
代码实现
b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;
总代码如下
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e3 + 10;
int a[N][N],b[N][N];
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++)
{
b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]; //存差分
}
}
while (q--)
{
int x1,y1,x2,y2,c;
scanf("%d %d %d %d %d",&x1,&y1,&x2,&y2,&c);
b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;
}
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
{
a[i][j] = b[i][j] + a[i-1][j] + a[i][j-1] - a[i-1][j-1]; //前缀和
}
}
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
{
printf("%d ",a[i][j]);
}
cout << endl;
}
}