【基础算法】前缀和(一维、二维)

本文介绍了前缀和的概念,强调了一维前缀和在计算区间和时的优势,尤其是在频繁查询时能显著提高效率。同时,详细讲解了二维前缀和的原理和应用,通过代码示例展示了如何求解一维和二维数组的前缀和以及区间和问题。
摘要由CSDN通过智能技术生成

一维前缀和

一维前缀和的作用

前缀和就相当于数列和。我们可以定义一个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;
}

 569800cd190c467c951a952288eaa4b0.png

因为求区间和时是闭区间,所以应当包括a[l]的值,故减去的前缀和的索引应该减1,如图中求[5,9]的区间和,减去的是s[4],而不是s[5]。

 

二维前缀和

二维前缀和作用

了解到一维前缀和的作用是快速计算一段区间的值的和,类比于二维,二维前缀和的作用就是快速求解一个矩阵的所有的值的和。

b4361ff3363e420ca04b36b8a1cad267.png

如图,如果你想要求图中蓝色边框围住的矩阵的和,你可以采用累加的形式:

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

 

二维前缀和求法

ffedb839f9a84d20b55c38cc35e1d312.png

例如要求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;
 } 

作者也是个初学者,在学习前缀和和差分时也是遇到很多困难,特别是二维的部分,理解这部分不容易,我也是思考了很久才最终弄懂。本人写这篇文章是想要分享自己的理解,帮助某些小伙伴理解这部分内容,当然,作者水平很菜,可能存在诸多错误,友友们发现了可以大胆指出,希望与诸位共同成长。 

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值