在算法和数据结构中,前缀和与差分是两个强大的工具,它们常常用于解决各种问题,并且可以在许多情况下优化算法的效率。本文将介绍前缀和的基本概念和应用场景,并通过示例代码来解释它们的使用方法。
目录
前缀和
前缀和的概念
前缀和是指一个数组中前缀部分的元素之和。通过预处理原始数组,我们可以使用前缀和来快速计算数组中任意子数组的和。前缀和的计算方法很简单:从数组的第一个元素开始,依次累加元素,将结果存储在一个新数组中。
前缀和的数学规律
从数学的概念上来讲,前缀和其实就是,也就是数列前n项的和 ,给大家复习一下:
假如有一个数列为:1、2、3、4、5、6、7.......、99、100,那么=1+2+3+4+5
也许大家会想,那直接利用数列和的公式不就好了吗?( )
但要是数列不是规则的呢?例如数列为:1、5、3、6、8、4、2、6、3.......
显然,这样的问题就要用到前缀和的代码来解决了。
为了方便后续对题目的处理,我们需要将前缀和的结果放到新的数组中,我们定义数组名为s,拿第一个有序数组举例:
a[]={1,2,3,4,5,6,7,8........,98,99,100}
也就是a[1]=1,a[2]=2,a[3]=3,a[4]=4.......a[98]=98,a[99]=99,a[100]=100
那么s[1]=1(1),s[2]=3(1+2),s[3]=6(1+2+3)........s[100]=5050(1+2+3+.....+100)
可以看到,s[1]就是第一项,s[2]就是数列第一项和第二项的和.....依次类推得到前缀和数组 s
代码如下:
#include <iostream>
using namespace std;
const int N=1e5+10;
int n;
int s[n],a[n];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];//构造原数列
}
for (int i = 1; i < n; i++) {
s[i] = s[i - 1] + a[i]; // 当前位置的累计和等于前一个位置的累计和加上当前元素
}
return 0;
}
不知道大家有没有发现一个细节,那就是为什么这里的原数列和前缀和数列我不从数组的0下表开始?对于前缀和数列其实很简单,因为我们在计算前缀和时会用到上一个前缀和的累计数,然后再加上当前原数列的数,如果前缀和数组s从0开始,就无法存储第一个前缀和s[1]。
前缀和数组不从0开始很好理解,可是为什么原数列也不从0开始呢?下面我们介绍到差分时就知道啦!!!
前缀和的用处
那我们得到了前缀和又有什么用处呢?
在有一些题目中,我们也许会遇到计算区间和的情况,例如给你一个区间[ l , r ],需要我们计算在原数列a中,l 到 r 之间的和是多少,。
设一下我们需要计算区间[5,99]的和,那目前我们有的数据是原数列a和前缀和数列s
第一种方法:从a[5]一直加到a[99]
这是最简单最暴力的做法了把?对于计算机来说这个确实很快,但是计算的时间略微有一点的长,如果范围很大,那就很棘手了。
第二种方法:s[99]-s[4]
根据我们学到的数学数列知识,我们要计算的是[5,99]区间内的数列和,而s[99]是前99项的和,s[5]是前5项的和, 那我们只要把前99项的和减去前4项的和,不就得到了[5,99]区间内的和了嘛?(因为第5项也包括在内,所以减去的是前面4项的和)
下面我们画图举例来理解一下:(全都是用的高中数列知识,不要想多!!!)
因此,我们要是要计算[l,r]之间的和,我们只需要计算 s[r]-s[l-1] 的值就可以了
二维前缀和
顾名思义,二维前缀和那肯定是用到二维数组了,那么对于一个二维平面,我们求的前缀和也就是在二维矩阵中的一个子矩阵的和 下面是一个二维原数列a[i][j],由于a的第0行和第0列依旧是没有数据的,所以不在图中标注,会在差分中详说。
那么二维前缀和是含义呢?假设二维前缀和数组为s,那么s[i][j]就是从a[0][0]开始,a[0~i][0~j]的所有元素的和,下面我们画图来描述一下:(假设我们要求出s[2][3],也就是从a[0~2][0~3]所有数据的和)
图中红色框框部分的所有数的和,就是s[2][3]的值了。
那我们要怎么像一位的前缀和一样得到二维前缀和数组呢??
我们不妨来找一找规律,我们首先构建一个二维原数组,然后通过手算的方式得到二维前缀和数组:
我们先看到s[2][2],它的值是12,是二维原数组中1+2+4+5得到的,但是我们从数学规律上也可以发现,它也可以通过s[1][2]+s[2][1]+a[2][2]-s[1][1]得到,那么前缀和数组的规律是否就是这样一个公式呢: s[i][j]=s[i-1][j]+s[i][j-1]+a[i][j]-s[i-1][j-1]得到的呢?
我们可以再拿s[3][3]再做一次验证,可以发现依然如此,所以二维前缀和数组就可以用这样的公式得到:s[i][j]=s[i-1][j]+s[i][j-1]+a[i][j]-s[i-1][j-1]
前面我们是用找规律的方法得出规律的,原理其实我们并不需要知道,只需要记住这个公式就可以了,但是想要推理的同学也可以自己推理一下,思想和一位前缀和差不多。
二维前缀和的用处
我们可以看到一位前缀和和二维前缀和其实有十分相似的地方,那么对于二维前缀和来说,他的用处是不是也是求出某一个区间的范围呢??答案必然是:正确!!
但是我们要注意的是,既然我们是二维前缀和,那我们的求解范围也一定是二维的,就比如我要求解a[2,2]到a[3][3]这个二维区间的和,我们用图表示一下:
也就是5+6+8+9=28
根据二维前缀和的原理,我们首先知道s[3][3]表示a[1~3][1~3]所有元素的和,那有没有想过,我们只需要将s[3][3]减去a[1][1]、a[1][2]、a[1][3]、a[2][1]、a[3][1]的值就可以了呢?
所以我们可以这样思考:(绿色的大框代表s[3][3],也就是a数组所有元素的和)
首先减去s[3][1](橙色划线区域的和) 再减去s[1][3](蓝色画线区域的和)
但是我们有没有发现,s[1][1]的这个位置被减去了两次?所以我们在最后的处理时,公式需要将s[1][1]重新加回去一个。注意并不是原数列的a[1][1]!!在上面的例子中由于凑巧a[1][1]=s[1][1],如果换个例子,重合的部分可能包含多个前缀和元素,我建议大家可以自己试一下求[2,3]到[3][3]的和就明白了。如下图:
那么我们最后得到的二维区间和a[2][2]到a[3][3]的答案为s[3][3]-s[3][1]-s[1][3]+s[1][1]
得到的最终公式为:
a[x1][y1]到a[x2][y2]的二维区间和=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]
下面给到例题和代码:
例题:子矩阵的和
#include<iostream>
using namespace std;
const int N=1010;
int n,m,q;
int a[N][N],s[N][N];
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++){
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
}
}
while(q--){
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]);
}
return 0;
}
那么简单的代码~不懂的盆友们就好好钻研一下吧~第二次发博客,略有不足,请积极指出!!!
太困了家人们,差分就明天......哦不,今天的晚上再更新吧,睡了55555