zoukankan      html  css  js  c++  java
  • 前缀和,二维前缀和与差分小总结

    在了解二维前缀和之前,我们首先需要了解一下什么是前缀和,一维前缀和。

    如果我给你一串长度为(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])就能得到我们想要的答案了,是不是很简单呢。

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

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

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

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

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

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

    #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)

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

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

    给定一个(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;
    }
    
  • 相关阅读:
    [Leetcode]7. 整数反转
    [Leetcode]6. Z 字形变换
    [Leetcode]5. 最长回文子串
    java实现各种排序算法1
    [Leetcode]4. 寻找两个正序数组的中位数
    css设置字体单行,多行超出省略号显示
    如何获得select被选中option的value和text和......
    在vue项目中,将juery设置为全局变量
    js中遍历对象的属性和值的方法
    深入理解JS的事件绑定、事件流模型
  • 原文地址:https://www.cnblogs.com/Xchu/p/11705054.html
Copyright © 2011-2022 走看看