【第一课】前缀和

文章讲述了作者在学习算法过程中的第一课,主要涉及前缀和的概念和应用。通过ACwing的795t和796t题目,作者从暴力解法开始,理解了时间复杂度,并介绍了如何通过前缀和优化代码,降低了时间复杂度。对于一维和二维数组,分别给出了前缀和的实现方式和代码示例。
摘要由CSDN通过智能技术生成

开个系列记录学姐带我学算法的过程啦

第一节课前缀和主要是通过两个题来理解。

acwing-795t

我在此之前完全没有接触过算法,只学了c语言和c++基础语法(说句题外话,其实c++或者说很多我们看着陌生的东西,内心好像感觉很难,但其实见得多了就习惯了,不要因为此时的你看不懂就觉得后面没法进行下去)。

所以在学姐提前发出这些题的时候,我采取的方式都是暴力做法😂

#include<stdio.h>

int Sum(int l,int r,int arr[])
{
    int sum=0;
    for(int i=l;i<=r;i++)
    {
        sum+=arr[i];
    }
    return sum;
}
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    int arr[n];
    for(int i=0;i<n;i++)
    {
        scanf("%d",&arr[i]);
    }
    int l,r;
    for(int i=0;i<m;i++)//这里有点混用了c++
    {
        scanf("%d %d",&l,&r);
        printf("%d\n",Sum(l-1,r-1,arr));
    }
}

由于在比赛过程中或者题目要求会对时间和空间有限制(我对这方面概念还不是很深 等之后有机会再说吧),我理解为代码运行的效率。下面我解释一下这段代码的时间复杂度,

补充一下时间复杂度:

理解为执行操作的次数。

当代码中没有递归或者循环时,例如

#include<stdio.h>
int main()
{
	int a,b,sum;
	scanf("%d%d",&a,&b);
	sum=a+b;
	printf("%d",sum);
	return 0;
}

此时时间复杂度为O(1),这意味着无论输入的整数有多大,这段代码都能在相同的时间内运行完毕。这是最低的时间复杂度,也是最理想的情况。

当存在并列循环的时候(没有嵌套),时间复杂度为循环次数相加。

当存在嵌套循环时,时间复杂度为循环次数相乘。

(时间复杂度先浅浅说到这里)

在这段代码主函数中并列for循环,且循环执行n+m次,此时是时间复杂度为O(n+m),但最后输出的时候,调用Sum函数中存在for循环,可理解为循环的嵌套,在Sum函数中,for循环执行次数为r-l+1,由于r和l是由输入决定的,我们不能确定它们的具体值,所以只能用最坏情况来估计时间复杂度。最坏情况是当r和l相差最大时,也就是r=n-1,l=0时,执行次数为n次,所以时间复杂度为  O(n*(m+1))。

为了降低时间复杂度,出现了更为高效的计算方法,前缀和算法,作用是快速地求出一个数组或矩阵中某个区间或子区域的和。

算法思想是:开辟一个新的数组,存放从第一个元素开始到当前元素的累加和,再通过前缀和之间的差来求出任意区间(一维)或子区域(二维)的和。

用图像的形式很清晰的看到我们所需要的两个数组对叭,如何用代码实现呢?p[i]就不用说啦,一个for循环就可以啦。主要是q[i],要明白它所表示的含义是,从第一个元素开始到当前元素的累加。用q[i]=q[i-1]+p[i] 的方式表示,想一下可以理解撒

当我们想要得到从第一个元素到第三个元素的和时,应该是q[3]-q[0]=6-0    从第二个元素到第四个元素的和时,应该是q[4]-q[1]=16-3      由此可以看出所求结果应该是q[r]-q[l-1]

完整代码如下,具体的细节都有注释

//前缀和算法
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,m;
int p[N],q[N];
//把变量定义到主函数外面 会自动初始化0
int main()
{
    //紧盯输入格式来写
    scanf("%d %d",&n,&m);//n表示数组元素个数,m表示询问次数(测试个数)
    for(int i=1;i<=n;i++)//这里使数组下标从1开始存储数据,避免了下面q[i-1]出错。而且由于将数组定义为全局变量,自动初始化为0,q[0]=0方便了很多
    {
        scanf("%d ",&p[i]);
        q[i]=q[i-1]+p[i];
    }
    while(m--)//m--作为循环判断条件 后置-- 先使用后--
    {
        int l,r;
        scanf("%d %d",&l,&r);
        printf("%d\n",q[r]-q[l-1]);
    }
    return 0;
}

ok啦,一维数组前缀和这道题就先到这里啦。

acwing-796t

有了上面的知识,对于二维数组我们来比葫芦画瓢一下。

首先需要开辟一个数组,来存放当前元素与第一个元素所围成区域的区域内的数字的总和(二维的)

是长这个样子的(q[i]也写出来是为了更清晰一点)。那么p[i]不用多说两个for循环嵌套输入即可,q[i]怎么得到呢?想一下一维的时候我们是通过它前一项加上这一点的值得到的,那二维应该也可以采用这种方式,如下图

假设(i,j) 是(2,3),那么它和坐标为(1,1)的第一个元素所围成的区域为图中红色框框,想要求得红框,就等于黄框+绿框-黄绿重叠部分+(i,j)即可。等式由此得来。

那么想要求任意(x1,y1)(x2,y2)所围成的区域的和

 

如图,找准点即可,且时刻记住q[i]这个数组存放的就是该点的前缀和(因为我发现我看不懂的时候就是因为忘记了q数组本身的含义emm)

完整代码如下

#include<iostream>
using namespace std;
const int N=1010;
int p[N][N],q[N][N];
int n,m,k;
int main()
{
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d ",&p[i][j]);
            q[i][j]=q[i-1][j]+q[i][j-1]-q[i-1][j-1]+p[i][j];
        }
    }
    while(k--)
    {
        int x1,y1,x2,y2;
        scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
        printf("%d\n",q[x2][y2]-q[x1-1][y2]-q[x2][y1-1]+q[x1-1][y1-1]);
    }
    return 0;
}

好啦。这节课的笔记先到这里啦。

本人是小白,算法学习才刚刚开始,也是第一次写博客(hhh其实看鹏哥c语言的时候就知道要写博客但是一直没开始😓),受到学姐的鼓励终于开始啦,相信对学习有帮助。欢迎交流和建议😄

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值