1、什么是一维差分?
一维差分是一维前缀和的逆运算,举个栗子:
原数组与差分数组的关系:原数组前后两项的差就是差分数组的值:(b[i]=a[i]-a[i-1])
对于第1个差分数组的值,(b[1]=a[1]-a[0]),因为(a[0])初始化为(0),所以(b[1]=a[1]),但也不妨碍我们按通用的方式进行理解。
差分数组与原数组的关系:差分数组的前缀和是原数组:(a[i]=b[1]+b[2]+b[3]+...+b[i])
2、一维差分的构建
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
上面就是构建的方法,我们来理解一下:
以(a_1=9,a_2=3,a_3=5,a_4=4,a_5=2)这个原始数组为例,根据上面的计算办法构建差分数组:
第一步:insert(1,1,9)
,就是l=1
,r=1
,c=9
,那么b[1]=9
,b[2]=-9
。
第二步:insert(2,2,3)
,就是l=2
,r=2
,c=3
,那么b[2]=-9+3=-6
,b[3]=0-3=-3
。
第三步:insert(3,3,5)
,就是l=3
,r=3
,c=5
,那么b[3]=-3+5=2
,b[4]=0-5=-5
。
第四步:insert(4,4,4)
,就是l=4
,r=4
,c=4
,那么b[4]=-5+4=-1
,b[2]=0-4=-4
。
第五步:insert(5,5,2)
,就是l=5
,r=5
,c=2
,那么b[5]=-4+2=-2
,b[2]=0-2=-2
。
3、一维差分用途
当我们要在某个区间([l,r])的所有值都加上一个数(x)时:我们只需要在差分数组中进行一加一减即可:(b[l]+c,b[r+1]−c)
为什么呢?
以(a[1]=9,a[2]=3,a[3]=5,a[4]=4,a[5]=2)这个原始数组为例,现在要地(a_2至a_4)这个范围内增加(2):
(a[1]=b[1])
(a[2]=b[1]+b[2])
(a[3]=b[1]+b[2]+b[3])
(a[4]=b[1]+b[2]+b[3]+b[4])
(a[5]=b[1]+b[2]+b[3]+b[4]+b[5])
(a[6]=b[1]+b[2]+b[3]+b[4]+b[5]+b[6])
现在要在(a_2至a_4)增加数字2,我们看看能不能通过增大(b)数组某两个值的方法对它们进行维护。
比如(b[2]+=2),那么受影响的是(a[2])及后面所有的(a)数组元素,包括(a[5],a[6]),这样还不行,因为我们只要求变化到(a[4]),看来还需要再做点什么,比如从(b[5])中再减去数字2,这时我们惊喜的发现,从 (a[5])开始,加2带来的负作用解除了!
3、差分数组与原数组一样长吗?为什么我看到了 b[r+1]=c,这样差分不就多了一个元素吗?
不是一样长的,差分数组其实是比原数组多一个元素的,多的那个在数组的最后面。
当然,一般我们是不用到最后面的那个元素的,因为,差分的概念都是描述当前位置和前一个位置的差,前缀和就是原数组了,最后一个生成的元素可以视为无效元素,没有必要再纠结了。
4、C++ 代码
#include<iostream>
using namespace std;
const int N=100010;
int n,m;
int a[N],b[N];
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[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++)b[i]+=b[i-1],printf("%d ",b[i]);
return 0;
}
5、二维差分概念
差分是前缀和的逆运算。就是如果有一个二维的数组b
,那么b[1][1]+....+b[i][j]
,就是一个矩形范围,它的最终加法结果值应该就是a[i][j]
.就是原数组i,j的位置值。
6、二维差分有什么用?
和一维差分的用途基本一致,在一个二维矩阵中,有多块区间需要增加或减少一个数值,多次操作后求最终的矩阵内容。如果按照传统办法,就是二层循环,复杂度很高,如果预处理出一个二维的差分矩阵,以后的多轮操作都转为了4次加加减减操作,可以视为(O(1))级别的时间复杂度,运算效率将得到极大提高。
7、看一下实际的例子
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
/**
测试用例:
原始数组
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
二维差分数组
1 1 1 1
4 0 0 0
4 0 0 0
4 0 0 0
*/
void insert(int x1, int y1, int x2, int y2, int c) {
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main() {
for (int i = 1; i <= 4; i++)
for (int j = 1; j <= 4; j++)
scanf("%d", &a[i][j]);
for (int i = 1; i <= 4; i++)
for (int j = 1; j <= 4; j++)
insert(i, j, i, j, a[i][j]);
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
printf("%d ", b[i][j]);
}
printf("
");
}
return 0;
}
8、二维差分公式推导
(① s[3][3] = b[1][1]+b[1][2]+b[1][3])
( + b[2][1]+b[2][2]+b[2][3])
( + b[3][1]+b[3][2]+b[3][3])
(② s[2][3] = b[1][1]+b[1][2]+b[1][3])
( + b[2][1]+b[2][2]+b[2][3])
(③ s[3][2] = b[1][1]+b[1][2])
( + b[2][1]+b[2][2])
( + b[3][1]+b[3][2])
(④ s[2][2] = b[1][1]+b[1][2])
( + b[2][1]+b[2][2])
(①-②-③+④)
(b[3][3]=s[3][3]-s[2][3]-s[3][2]+s[2][2])
抽象一下,就可以获取到从原矩阵获取差分矩阵的公式
:
(b[i][j]=s[i][j]-s[i-1][j]-s[i][j-1]+s[i-1][j-1])
如何从差分矩阵得到原矩阵呢?可以参考下面公式(就是二维前缀和)
(s[i][j]=b[i][j]+b[i-1][j]+b[i][j-1]-b[i-1][j-1])
9、二维差分如何构建?
如果我们要在左上角是 (x1,y1)
,右下角是 (x2,y2)
的矩形区间每个值都 +c
,如下图所示
在我们要的区间开始位置(x1,y1)
处 +c
,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分 -c
消除 +c
的影响,而两个蓝色部分重叠的绿色部分多了个 -c
的影响,所以绿色部分 +c
消除影响。所以对应的计算方法如下:
void insert(int x1, int y1, int x2, int y2, int c){
b[x1][y1] += c; //开始位置增加C
b[x2 + 1][y1] -= c; //见上面的图,把第二行的蓝色区域减去C
b[x1][y2 + 1] -= c; //见上面的图,把第一行的蓝色区域减去C
b[x2 + 1][y2 + 1] += c; //交叉位置被减了两次,需要补回来。
}
10、C++ 代码
#include<iostream>
using namespace std;
const int N=1010;
int n,m,q;
int a[N][N],b[N][N];
void insert(int x1, int y1, int x2, int y2, int c){
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main(){
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++)
b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
printf("%d ",b[i][j]);
puts("");
}
return 0;
}