【引入】
首先给出一个问题:
给定n个数,再给出m个询问,每个询问给出区间Li,Ri和x,要求你在Li到Ri上每一个值都加上x,最后给出一个询问区间L,R的区间和,怎么办?
思考一下:如果暴力,最坏时间复杂度O(n^2);线段树或者树状数组,时间复杂度O(logn);而使用差分可以O(n)。
要使用差分,首先我们来谈谈前缀和。
【前缀和】
什么是前缀和?前缀和是一个数组的某项下标之前(包括此项元素)的所有数组元素的和。
设b[]为前缀和数组,a[]为原数组,根据这句话可以得到前缀和的定义式和递推式:
定义式 | 递推式 | |
一维前缀和 | ||
二维前缀和 |
一维前缀和理解起来比较容易,二维前缀和后边细说,我们先来说说差分。
【差分】
什么是差分?差分是一个数组相邻两元素的差,一般为下标靠后的减去靠前的一个。设差分数组p[],即:
【联系】
前缀和和差分有什么关系呢?
令F(a)表示前缀和数组,G(a)表示差分数组,则
可以说 前缀和 和 差分 是一对互逆过程。
【一维前缀和】
根据上述表达式我们可以以O(1)求出区间[i,j]的区间和
【二维前缀和】
二维前缀和的计算运用了容斥定理,我们来看下图:
显然左上角重合的那一块加了两次,所以我们需要减掉重合的部分。请结合上图理解下列递推式 (*^▽^*) 。
【应用】
回到引入中的问题,对于每一个L,R,x,我们只需要在b[L]+x,在b[R]-x,最后做前缀和,O(1)查询即可。
代码如下:
#include
int a[1005];
int b[1005];
int main()
{
int n,m; scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
while(m--){
int L,R,x;
scanf("%d%d%d",&L,&R,&x);
b[L]+=x;
b[R+1]-=x;
}
int sum=0;
for(int i=1;i<=n;i++){
sum+=b[i];
a[i]+=a[i-1]+sum;
}
int l,r; scanf("%d%d",&l,&r);
printf("%d\n",a[r]-a[l-1]);
return 0;
}
还是引入中的问题,升级为:
给定一个n*m大小的矩阵a,再给出m个询问,每次询问给定x1,y1,x2,y2和x,表示以(x1,y1)为左上角坐标和(x2,y2)为右下角坐标的子矩阵的所有元素都加上x,最后给出一个询问:以(x1,y1)为左上角坐标和(x2,y2)为右下角坐标的子矩阵的所有元素和,怎么办?
对于每一个x1,y1,x2,y2,我们只需要对数组b
b[x1][y1]+=p; b[x2+1][y2+1]+=p;
b[x2+1][y1]-=p; b[x1][y2+1]-=p;
然后做二维前缀和即可。
例题:Monitor(二维前缀和+差分)