前缀和,二维前缀和与差分小总结

在了解二维前缀和以前,咱们首先须要了解一下什么是前缀和,一维前缀和。c++

若是我给你一串长度为\(n\)的数列\(a1,a2,a3......an\),再给出\(m\)个询问,每次询问给出\(L,R\)两个数,要求给出区间\([L,R]\)里的数的和,你会怎么作,如果没有了解过前缀和的人看到这道题的想法多是对于\(m\)次询问,我每次都遍历一遍它给的区间,计算出答案,这样子的方法当然没错,可是其时间复杂度达到了\(O(n*m)\),若是数据量稍微大一点就有可能超时,而咱们若是使用前缀和的方法来作的话就可以将时间复杂度降到\(O(n+m)\),大大节省了运算时间。至于怎么用,请看下面一小段代码数组

a[0]=0;

for(int i=1;i<=n;i++)a[i]+=a[i-1];

没错,前缀和顾名思义就是前面\(i\)个数的总和。数组a在通过这样的操做以后,对于每次的询问,咱们只须要计算\(a[R]-a[L-1]\)就能获得咱们想要的答案了,是否是很简单呢。spa

在知道了最简单的前缀和以后,咱们再来了解一下什么是差分。.net

给你一串长度为\(n\)的数列\(a1,a2,a3......an,\)要求对\(a[L]~a[R]\)进行\(m\)次操做:code

操做一:将\(a[L]~a[R]\)内的元素都加上\(P\)blog

操做二:将\(a[L]~a[R]\)内的元素都减去\(P\)ci

最后再给出一个询问求\(a[L]-a[R]\)内的元素之和?get

你会怎么作呢?你可能会想,我对于\(m\)次操做每次都遍历一遍\(a[L]~a[R]\),给区间里的数都加上\(P\)或减去\(P\),最后再求一次前缀和就好了。没错,这样子确实也能得出正确答案,但时间复杂度却高达\(O(M*n)\),对于\(1<=n,m<=1e5\)这个数据范围来讲直接就\(TLE\)了,因此说这个方法不可行。既然这样不行的话,那咱们要怎么作才能快速的获得正确答案呢?是的,这个时候咱们的差分就该派上用场了,咱们新开一个数组\(b\),储存每一次的修改操做,最后求前缀和的时候统计一下就能快速的获得正确答案了,详细请看下面代码。
简简单单it

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+9;
int a[maxn],b[maxn];
int main(){
    int i,j,k,n,m,p;
    cin>>n>>m;
    for(i=1;i<=n;i++){
        cin>>a[i];
    }
    for(i=1;i<=m;i++){
        int L,R,t;
        cin>>t>>L>>R>>p;
        if(t==1){
            b[L]+=p;b[R+1]-=p; //仔细想一想为何b[R+1]要减去p 
        }
        else{
            b[L]-=p;b[R+1]+=p;
        }
    }
    int add=0;
    for(i=1;i<=n;i++){
        add+=b[i];
        a[i]+=a[i-1]+add;
    }
    int x,y;
    cin>>x>>y;
    cout<<a[y]-a[x-1]<<endl;
}

相信看到这里,你们已经仔细思考过代码了,为何操做一时\(b[R+1]\)要减去\(p\),很简单,由于操做一我只需对\([L,R]\)区间里的数加\(p\)\([R+1,n]\)这个区间里的数不必加\(p\),因此须要减掉\(p\)class

差分讲解完毕,接下来咱们终于要开始今天的正题——二维前缀和了。

仍是以小问题的形式来说解二维前缀和吧

给定一个\(n*m\)大小的矩阵\(a\),有\(q\)次询问,每次询问给定\(x1,y1,x2,y2\)四个数,求以\((x1,y1)\)为左上角坐标和\((x2,y2)\)为右下角坐标的子矩阵的全部元素和。注意仍然包含左上角和右下角的元素。

怎么作呢?为了方便大家理解,上个图吧。


如图所示,按题目要求,咱们每次要求的答案就是红色圆圈所在的区域的值(注意,这里的\(x1,x2\)表示行,\(y1,y2\)表示列),对比上面这张图咱们可以发现红色区域的值等于四个区域的值减去(白色区域+黑色区域),再减去(白色区域+蓝色区域),最后由于白色区域被减了两次,咱们须要再加回来。因此\(ans=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]\);(注意,此时的\(a\)数组表明的是前缀和)。忽然想起来还没说怎么求二维前缀和,很简单,看下面代码。

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];

为方便理解贴个图

假如我想\(求a[2][4]\)的前缀和,我得先加上\(a[1][4]\)的前缀和,再加上\(a[2][3]\)的前缀和,而后这个时候咱们发现实际上\(a[1][3]\)这个部分咱们加了两遍,因此咱们须要再减去一遍\(a[1][3]\),因而得出公式\(a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1]\)

接下来看完整代码吧。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+9;
int a[maxn][maxn];
int main(){
    int i,j,k,n,m,q;
    cin>>n>>m>>q;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++)
        cin>>a[i][j];
    }
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++)
        a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
    }
    for(i=1;i<=q;i++){
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        int ans=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];
        cout<<ans<<endl;
    }
}

是否是感受还挺简单
在学完二维前缀和以后,一些同窗可能会有疑问,一维前缀和能用上差分,那么二维前缀和能不能用上差分呢?答案是确定的。

那么怎么差分呢?方法是和一维相似的,咱们也是须要另开一个数组记录修改操做,最后求前缀和时统计修改操做,只是二维每一次操做须要记录4个位置,一维只须要记录2个位置。具体怎么作,看下面代码吧。

for(int i=0;i<m;i++){//m是修改操做次数 
    int x1,y1,x2,y2,p;
    cin>>x1>>y1>>x2>>y2>>p;
    b[x1][y1]+=p;b[x2+1][y2+1]+=p;
    b[x2+1][y1]-=p;b[x1][y2+1]-=p;
}
相关文章
相关标签/搜索