目录
一、概念
一维差分:对于一个给定的数列A,其差分数列B定义为B[1]=A[1],B[i]=A[i]-A[i-1](2<=i<=n),且“差分”与“前缀和”是一对互逆运算,差分序列的前缀和序列就是原序列A。
二维差分:对于一个给定的二维数组A,其差分数组B定义为B[i][j]=A[i][j]-A[i-1][j]-A[i][j-1]+A[i-1][i-1](1<=i<=n)
二、操作解释
1.一维差分
a.区间数据修改
如图1所示样例,在a数组1~3区间内都加上1。通过差分数组,我们可以将差分数组下标为1的数加上1,在下标为3的数的后面一个数减去1,通过求b数组的前缀和数组后会发现,得到的数据等价于在a数组的1~3区间内都加上1。
void get_b(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
get_b(l,r,c);
b.差分数组初始化
以图2为例,初始化差分数组b。可以将初始化的过程看做是在b数组内由1开始到n,每一个长度为1的区间(i,i)内加上与a数组对应的数据a[i],如此也满足差分数组的条件。
![](https://i-blog.csdnimg.cn/blog_migrate/e90fad39aa5ccaad4e7e15f212585e70.png)
///在单独一个i区间内插入数据a[i],来初始化差分数组,
for(int i=1;i<=n;i++) get_b(i,i,a[i]);
c.前缀和求修改后的原始数据
///存储差分数组b的前缀和数组,对应的第i个数据
for(int i=1;i<=n;i++) {
b[i]+=b[i-1];
}
2.二维差分
a.区间数据修改
以图 3为例,在a数组中以(x1,y1)为矩阵的左上角坐标,以(x2,y2)为矩阵的右下角坐标 ,形成的小矩阵内每一个值都加上一个值c。因为“差分”与“前缀和”为互逆运算,若a的差分数组b求a数组的一个数a[i][j],等于将以(1,1)为矩阵左上角坐标,(i,j)为矩阵的右下角坐标内所有的b数组元素都加起来。将a数组中(x1,y1)和(x2,y2)形成的小矩阵内的元素都加上c,其实就是让其内的每个元素的差分数组所累加起来的的数中加上添加的元素c。所以如图,在b数组矩阵的左上角坐标上加上一个数c,就能够使该矩阵内的每一个位置上涵盖一个增加的元素c,此外在(x1,y2+1)、(x2,y1+1)、(x2+1,y2+1)上进行-c,-c,+c的操作是为了让其他位置的数据不被修改。
![](https://i-blog.csdnimg.cn/blog_migrate/6e1eb9644cfc22f0005bee01688ee79a.png)
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 x1, x2, y1, y2, c;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, y2, c);
b.差分数组初始化
以图4为例,初始化二维的差分数组。构造a的差分数组b,可以看做在每一个(i,j)和(i,j),所形成的小矩阵内添加元素a[i][j],将矩阵内的每一个位置都遍历一遍,每一个位置的差分数组元素都通过该方式构成。
![](https://i-blog.csdnimg.cn/blog_migrate/d5018b362680acc2005f942c45d92f5e.png)
c.前缀和求修改后的原始数据
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
///求差分数组b的前缀和,并将该值存入b数中
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
printf("%d ", b[i][j]);
}
puts(" ");
}
三、例题分析
1.一维差分
a.题目描述
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c表示一个操作。
输出格式
共一行,包含 n 个整数,表示最终序列。
数据范围
11≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
b.解题思路
思路:用差分来维护区间内数据的增减变化
c.代码实现
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
void get_b(int l,int r,int 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]);
///在单独一个i区间内插入数据a[i],来初始化差分数组,
for(int i=1;i<=n;i++) get_b(i,i,a[i]);
while(m--){
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
get_b(l,r,c);
}
///存储差分数组b的前缀和数组,对应的第i个数据
for(int i=1;i<=n;i++) {
b[i]+=b[i-1];
cout <<b[i]<<' ';
}
return 0;
}
2.二维差分
a.题目描述
输入一个 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
b.解题思路
思路:用二维差分来维护矩阵内数据的增减变化
c.代码实现
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
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, x2, y1, 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++) {
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
printf("%d ", b[i][j]);
}
puts(" ");
}
return 0;
}