差分及模板
1.差分的定义及问题引出
给定一个数组:A = a1,a2,...,an
现在构造一个数组B = b1,b2,...,bn,使得:
ai = b1+b2+...+bi
那么B就是A的差分。
根据前缀和的定义,我们发现:差分就是前缀和的逆运算。
根据上述定义,引出两个问题:
1. 如何构造差分B?
2. 差分的作用?
2. 一维差分的构造及作用
我们可以根据如下公式,来进行构造差分B:
b1 = a1
b2 = a2 - a1
b3 = a3 - a2
...
bn = an - an-1
但是,我们不打算以这种方式来构造差分数组B,我们在之后进行讲解。
那么,差分的作用是什么呢?
1.假设,已经构造出来了差分数组B,那么如果想求原数组A,那么只需要对差分数组B求前缀和即可。换句话说,只要有差分数组B,那么我们就可以以O(n)的时间复杂度来求出原数组A。
2.有这样一个应用场景:现在我们想让原数组A当中[l,r]区间内的所有元素全部加上一个常数C(或其余类似操作)。如果用暴力来做的话,就是O(n)。但是,如果我们对差分数组B操作的话,时间复杂度就是O(1)。具体如下:
1. 首先让bl+C,bl+C的作用就是:原数组al之后(包括al)的所有元素都加上了常数C。
2. 再让br+1-C,br+1-C的作用就是:原数组ar+1之后(包括ar+1)的所有元素都减去了C。
3. 经过这两个步骤之后,我们就会发现:al之后的所有元素加上了C,而ar+1之后的元素都减去了C。这样的话就保证了原数组al-ar之间的所有元素都加上了C,而原数组其余元素保持不变。
因此,原来对原数组O(n)的操作,在差分数组B上操作就变成了O(1)。
完成上述操作之后,再对修改后的差分数组求前缀和之后,就可以得到满足条件的A数组了。
如何构造差分数组B?
假设我们已经拥有了原数组A,那么如何构造差分数组B?
1. 我们可以假定:原数组A都为0,那么A对应的差分数组B也都为0。
2. 但是,原数组A已经给出。那么,原数组A可以看作都为0的差分数组B进行n次插入操作后,再通过对差分数组B进行前缀和而得出来的数组。换句话说,想要得到原数组A,就需要得到A对应的差分数组B。差分数组B实际上就是先初始化为0,再进行n次插入操作而得到。那么插入操作具体如何?
3. 对初始化为0的差分数组B进行n次插入操作实际上如下:
1. 对[1,1]区间加上a1。实际上为:b[1] += a[1] b[1+1] -= a[1]。这样的效果就是:a1之后的元素包括a1,都加上了a[1],a2之后的元素包括a2,都减去了a[1]。若如此做原数组只初始化了a1 = a[1]。
2. 对[2,2]区间加上a2。操作如上。
3. ...
4. 对[n,n]区间加上an。
注意:前缀和和差分的问题下标都从1开始,方便编程。
3. 一维差分的模板
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
4. 一维差分的例题
https://www.acwing.com/problem/content/799/
#include <iostream>
#include <cstdio>
using namespace std;
int a[100010],b[100010];
void insert(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]);
}
for(int i=1;i<=n;i++){
insert(i,i,a[i]);
}
int l,r,c;
while(m--){
scanf("%d %d %d",&l,&r,&c);
insert(l,r,c);
}
for(int i=1;i<=n;i++){
b[i] += b[i-1];
}
for(int i=1;i<=n;i++){
printf("%d ",b[i]);
}
return 0;
}
5. 二维差分的定义及问题引出
二维差分和一维是类似的。假设我们有原矩阵A,A中i行j列的元素为aij,那么差分矩阵的定义就是:存在一个矩阵B,B中i行j列的元素为bij,使得aij为bij的左上角元素之和。即:
aij = b11 + b12 + ... + b1j + ... + bi1 + bi2 + ... + bij。
根据上述定义,引出两个问题:
1. 如何构造差分矩阵B?
2. 差分的作用?
6. 二维差分的构造及作用
那么,二维差分的作用是什么呢?假设有这样一个场景:我们需要给原矩阵中的子矩阵加上一个常数C,那么如果在原矩阵中直接操作,那么就是O(n*m)。如果在差分矩阵中进行操作,时间复杂度就是O(1)。再通过求差分矩阵的前缀和之后,就得到了修改后的新矩阵。
那么,如何完成上述的插入操作呢?
假设,我们需要给(x1,y1)和(x2,y2)所围成的子矩阵加上常数C,操作如下:
1. 先让bx1y1 += C,根据二维差分的定义,这样做的效果,就是在原矩阵A中ax1y1左下角的所有元素都加上了C。
2. 再让bx2+1,y1 -= C。
3. 再让bx1,y2+1 -= C。
4. 再让bx2+1,y2+1 += C。
经过上述四个步骤后,再对二维差分矩阵求前缀和,就得到了更新之后的新数组A.
二维差分也是二维前缀和的逆运算。关于如何构造差分矩阵B,跟构造差分数组是类似的。假设原矩阵A中的每个元素均为0,那么其差分矩阵自然为0。然后,遍历差分矩阵中的每一个元素作n*m次插入操作即可。具体如下:(跟差分数组的初始化是类似的)
1. 在插入的时候,可以将差分矩阵中的每一个元素看成一个子矩阵进行插入。
2. 插入的值就是原矩阵中的元素aij。
3. 确定好范围和值后,再根据上述的四个操作进行n*m次插入即可。
4. 这样的话,就完成了差分矩阵的初始化,如若对差分矩阵求前缀和,那么得到的就是原矩阵A。
7. 二维差分模板
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
8. 二维差分例题
https://www.acwing.com/problem/content/800/
#include <iostream>
#include <cstdio>
using namespace std;
int a[1010][1010];
int b[1010][1010];
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(){
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++){
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]);
}
printf("\n");
}
return 0;
}
作者:gao79138
链接:https://www.acwing.com/
来源:本博客中的截图、代码模板及题目地址均来自于Acwing。其余内容均为作者原创。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现