暑期算法集训之前缀和篇:优化算法的利器

在算法和数据结构中,前缀和与差分是两个强大的工具,它们常常用于解决各种问题,并且可以在许多情况下优化算法的效率。本文将介绍前缀和的基本概念和应用场景,并通过示例代码来解释它们的使用方法。

目录

前缀和

前缀和的概念

前缀和的数学规律

前缀和的用处

二维前缀和 

二维前缀和的用处


前缀和

前缀和的概念

前缀和是指一个数组中前缀部分的元素之和。通过预处理原始数组,我们可以使用前缀和来快速计算数组中任意子数组的和。前缀和的计算方法很简单:从数组的第一个元素开始,依次累加元素,将结果存储在一个新数组中。

前缀和的数学规律

从数学的概念上来讲,前缀和其实就是S_{n},也就是数列前n项的和 ,给大家复习一下:

假如有一个数列为:1、2、3、4、5、6、7.......、99、100,那么S_{5}=1+2+3+4+5

也许大家会想,那直接利用数列和的公式不就好了吗?( \frac{(1+n)n}{2} )

但要是数列不是规则的呢?例如数列为: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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LifeGPT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值