一维前缀和
一维前缀和的作用
前缀和就相当于数列和。我们可以定义一个S[n]来表示a[0]+a[1]+a[2]+a[3]+....+a[n],所以当要求一个索引为[l,r]的区间和时,即a[l]+a[l+1]+....+a[r],可以直接用S[r]-S[l-1]。
如果只用求一次区间和,我们大可不必求前缀和,直接for循环遍历[l,r]的a数组,累加即可,在这种情况下,累加的操作最多为n次,复杂度为O(n);但假如要求m次区间和,for循环的操作最多为m*n次,复杂度为O(m*n)。而用前缀和的话,只需求前缀和时操作n次,再操作m次求区间和,复杂度为O(n)。
当求和次数很多时,前缀和能够极大减少时间消耗。
一维前缀和代码
#include <stdio.h>
//在main()函数外定义数组能够使数组所有的初始值为0
int a[100010];
int s[100010];
//数组大小要根据题目给的n值大小确定,如n<=10e5
int main(void)
{
//n个数,m次求区间和
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
//求前缀和
for(int i=1;i<=n;i++){
s[i] = s[i-1] + a[i];
}
//m次求区间和
int m;
scanf("%d",&m);
int l,r,result;
while(m--){
scanf("%d %d",&l,&r);
result = s[r] - s[l-1];
printf("%d\n",result);
}
return 0;
}
因为求区间和时是闭区间,所以应当包括a[l]的值,故减去的前缀和的索引应该减1,如图中求[5,9]的区间和,减去的是s[4],而不是s[5]。
二维前缀和
二维前缀和作用
了解到一维前缀和的作用是快速计算一段区间的值的和,类比于二维,二维前缀和的作用就是快速求解一个矩阵的所有的值的和。
如图,如果你想要求图中蓝色边框围住的矩阵的和,你可以采用累加的形式:
int sum = 0;
for(int i=3;i<=5;i++){
for(int j=3;j<=5;j++){
sum = sum + a[i][j];
}
}
这是求一次的结果,同样的,如果题目要求你求m次某个矩阵和时,你也要执行 m*i*j次累加操作吗,当m数值一大,题目必然超时。
如果能够在前面求得二维数组中所有矩阵的前缀和,那么我们在执行每次询问时,只需一条表达式便可求出我们需要的矩阵的和的值。
例如图中需要求蓝色边框围住的矩阵和,只需用绿色边框围住的矩阵和减去黑色和红色边框围住的矩阵和,再加上黑色和红色边框围住的矩阵的重叠部分的和,即 s[5][5] - s[2][5] - s[5][2] + s[2][2]。
由此可以总结出,当题目要求m次询问时,每次询问输入4个值,分别为i1,j1,i2,j2,要求求出(i1,j1)到(i2,j2)的矩阵和,可以用公式 result = s[i2][j2] - s[i1-1][j2] - s[i2][j1-1] + s[i1-1][j1-1]。
二维前缀和求法
例如要求s[i][j],也就是蓝色框,可以有红色框加黄色框再减去灰色部分,最后加上a[i][j]。
用公式即为s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]。
可以简单记为左 + 上 -左上 + 自己。
因为求前缀和时,我们需要知道‘左’、‘上’和‘左上’的值,所以在定义前缀和数组时,可以在main函数外定义,这样数组所有的初始值都为0,就可以从i=1,j=1开始求前缀和。要注意,应当从左到右,从上到下依次求前缀和,这样才能保证每一次求前缀和时‘左’、‘上’和‘左上’的值都存在且有效。
代码演示:
#include <stdio.h>
int a[100][100];
int s[100][100]; //数组大小要看题目要求
int main(void)
{
//按照题目要求确定矩阵长,宽
int n,m;
scanf("%d %d",&n,&m);
//初始化a数组
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++){
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
}
}
//k次询问求(i1,j1)到(i2,j2)的矩阵和
int k;
scanf("%d",&k);
int i1,j1,i2,j2;
while(k--){
int result;
scanf("%d %d %d %d",&i1,&j1,&i2,&j2);
result = s[i2][j2] - s[i1-1][j2] - s[i2][j1-1] +s[i1-1][j1-1];
printf("%d\n",result);
}
return 0;
}
作者也是个初学者,在学习前缀和和差分时也是遇到很多困难,特别是二维的部分,理解这部分不容易,我也是思考了很久才最终弄懂。本人写这篇文章是想要分享自己的理解,帮助某些小伙伴理解这部分内容,当然,作者水平很菜,可能存在诸多错误,友友们发现了可以大胆指出,希望与诸位共同成长。