草庐IT

【算法】手把手学会前缀和

LinAlpaca 2023-05-15 原文

目录

前缀和

前缀和的好处

公式的推导

例题:前缀和

二维前缀和

推导公式

 例题: 子矩阵的和


前缀和

前缀和的好处

🎵前缀和算法可以理解为是一种以空间换时间的方式,通过建立一个新的数组来存储从头到当前位置的数据的总和

公式的推导

初始化数组

 🎵前缀和数组的初始化就是将前 个数存在一个新的数组之中,这里用 表示原数组,表示前缀和数组。在计算时,由于当前 i 位置的上一位表示的就是1 ~ i-1的前缀和,于是我们便可以在此基础上加上原数组上的值(a[i])就是当前位置的前缀和了。

🎵于是初始化的公式便优化成s[i] = s[i-1] + a[i]

🎵也正是这个原因前缀和数组的下标必须从1开始,且默认s[0] = 0 才能保证s[1]就是a[1]的本身。如此循环一趟便可完成前缀和数组的初始化。

区间和

🎵求区间和才是特意建立前缀和数组的目的,要求区间和的时候若我们每次都暴力地直接进行区间遍历,随着区间长度和计算次数的增加,会出现非常多次的重复计算。

🎵若提前用前缀和数组记录下来就可以将每次计算的时间复杂度优化到 O(1) ,极大地优化了代码的效率。那前缀和数组怎么求区间和呢,让我们现在来看看。

 🎵我们设左边界为 ,右边界为 ,根据性质便可以得到如下等式,若要求数组的一段区间和,只需要在前缀和数组中用 的值减去 l-1 的值,此行为即表示将左边界之前的值删去,那剩下的便是我们要求的区间和了。

例题:前缀和

🎵 传送门:AcWing 795. 前缀和

🎵题目要求使用前缀和数组进行区间数的求和,就跟我们上面推导的公式一样,只需要初始化完前缀和数组后,根据公式输出结果就能够完成题目要求。更多细节我们通过代码来看看。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;
int n,m;
int s[N],a[N];

int main() 
{
	scanf("%d%d",&n,&m);
	for(int i = 1;i<=n;i++)
	{
		scanf("%d",&a[i]);     //读入原数组
		s[i] = s[i-1] + a[i];  //初始化前缀和数组
	}
	while(m--)  //  m组数据
	{
		int l,r;
		scanf("%d%d",&l,&r);
		printf("%d\n",s[r]-s[l-1]);  //根据边界求区间和
	}
	return 0;
}

二维前缀和

🎵二维前缀和便是在一维前缀和的基础上,拓展了一个维度,在(i, j)位置表达的便是从(1,1)开始一直到(i, j)这个范围内所有的值的和。注意: 这里画的图都是以(1,1)为起点进行表示的。

推导公式

初始化

🎵这里从图示出发,我们也很容易观察到,若我们想要对其进行初始化只需要,用上部分矩阵加上左部分矩阵,但这个过程中中间部分被算了两次所以需要扣掉一次,最后再补上当前位置的值即可,即s[i,j] = a[i, j] + s[i, j-1] + s[i-1, j] - s[i-1, j-1]

求区间和

🎵假设求的是 (x1,y1) 到 (x2,y2) 的区间和,我们可以看到目标区间就是紫色的矩阵,就像求一维前缀和那样,我们只要保留目标区间,其他多余的部分都要删除,现在我们要删除掉的就是目标矩阵上方左边的两个矩阵,即 s[x1-1, y2] 和 s[x2, y1-1] ,很明显两者有一个重复的空间s[x1-1, y1-1](数据再大一点就不仅仅只是一格而已),而连续扣掉两个矩阵后这个重复的空间便会被减去两遍,于是我们需要将其加一遍回来。

🎵即 s[x1y1,x2y2] = s[x2, y2] - s[x2, y1-1] - s[x1-1, y2] + s[x1-1, y1-1]

 例题: 子矩阵的和

🎵 传送门: AcWing 796. 子矩阵的和

🎵 通过上面的讲解,这道题目就变得相当简单了,根据题目要求建立二维前缀和数组,之后根据公式求区间和,便可完成任务要求。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1010;
int a[N][N], s[N][N];
int n, m, q;

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++)
		{
			s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];  //初始化二维前缀和数组
		}
	}
	while (q--)
	{
		int x1, y1, x2, y2;
		scanf("%d%d%d%d", &x1, &y1, &x2, &y2);  //接收区间
		printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]); //根据区间和的公式求区间和
	}
	return 0;
}

🎵 好了这次前缀和数组的入门讲解到这里就结束了,如果对你有用的话还请留下你的三连加关注。关注博主,共同进步!!!

有关【算法】手把手学会前缀和的更多相关文章

  1. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  2. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  3. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  4. ruby - 在 Ruby 中实现 Luhn 算法 - 2

    我一直在尝试用Ruby实现Luhn算法。我一直在执行以下步骤:该公式根据其包含的校验位验证数字,该校验位通常附加到部分帐号以生成完整帐号。此帐号必须通过以下测试:从最右边的校验位开始向左移动,每第二个数字的值加倍。将乘积的数字(例如,10=1+0=1、14=1+4=5)与原始数字的未加倍数字相加。如果总模10等于0(如果总和以零结尾),则根据Luhn公式该数字有效;否则无效。http://en.wikipedia.org/wiki/Luhn_algorithm这是我想出的:defvalidCreditCard(cardNumber)sum=0nums=cardNumber.to_s.s

  5. Ruby 斐波那契算法 - 2

    下面是我写的一个计算斐波那契数列中的值的方法:deffib(n)ifn==0return0endifn==1return1endifn>=2returnfib(n-1)+(fib(n-2))endend它工作到n=14,但在那之后我收到一条消息说程序响应时间太长(我正在使用repl.it)。有人知道为什么会这样吗? 最佳答案 Naivefibonacci进行了大量的重复计算-在fib(14)fib(4)中计算了很多次。您可以将内存添加到您的算法中以使其更快:deffib(n,memo={})ifn==0||n==1returnnen

  6. ruby - 为什么 ruby​​ 中的变量前缀允许在方法调用中省略括号? - 2

    在DavidFlanagan的TheRubyProgrammingLanguage中;松本幸弘theystatethatthevariableprefixes($,@,@@)areonepricewepayforbeingabletoomitparenthesesaroundmethodinvocations.谁可以给我解释一下这个? 最佳答案 这是我不成熟的意见。如果我错了,请纠正我。假设实例变量没有@前缀,那么我们如何声明一个实例变量?classMyClassdefinitialize#Herefooisaninstanceva

  7. ruby-on-rails - Rails add_index 算法 : :concurrently still causes database lock up during migration - 2

    为了防止在迁移到生产站点期间出现数据库事务错误,我们遵循了https://github.com/LendingHome/zero_downtime_migrations中列出的建议。(具体由https://robots.thoughtbot.com/how-to-create-postgres-indexes-concurrently-in概述),但在特别大的表上创建索引期间,即使是索引创建的“并发”方法也会锁定表并导致该表上的任何ActiveRecord创建或更新导致各自的事务失败有PG::InFailedSqlTransaction异常。下面是我们运行Rails4.2(使用Acti

  8. ruby - 趋势算法 - 2

    我正在开发一个类似微论坛的项目,其中一个特殊用户发布一条快速(接近推文大小)的主题消息,订阅者可以用他们自己的类似大小的消息来响应。直截了当,没有任何形式的“挖掘”或投票,只是每个主题消息的响应按时间顺序排列。但预计会有很高的流量。我们想根据它们引起的响应嗡嗡声来标记主题消息,使用0到10的等级。在谷歌上搜索了一段时间的趋势算法和开源社区应用示例,到目前为止已经收集到两个有趣的引用资料,但我还没有完全理解它们:Understandingalgorithmsformeasuringtrends,关于使用基线趋势算法比较维基百科页面浏览量的讨论,在SO上。TheBritneySpearsP

  9. Ruby - 不支持的密码算法 (AES-256-GCM) - 2

    我收到错误:unsupportedcipheralgorithm(AES-256-GCM)(RuntimeError)但我似乎具备所有要求:ruby版本:$ruby--versionruby2.1.2p95OpenSSL会列出gcm:$opensslenc-help2>&1|grepgcm-aes-128-ecb-aes-128-gcm-aes-128-ofb-aes-192-ecb-aes-192-gcm-aes-192-ofb-aes-256-ecb-aes-256-gcm-aes-256-ofbRuby解释器:$irb2.1.2:001>require'openssl';puts

  10. java实现Dijkstra算法 - 2

    文章目录一.Dijkstra算法想解决的问题二.Dijkstra算法理论三.java代码实现一.Dijkstra算法想解决的问题解决的问题:求解单源最短路径,即各个节点到达源点的最短路径或权值考察其他所有节点到源点的最短路径和长度局限性:无法解决权值为负数的情况二.Dijkstra算法理论参数:S记录当前已经处理过的源点到最短节点U记录还未处理的节点dist[]记录各个节点到起始节点的最短权值path[]记录各个节点的上一级节点(用来联系该节点到起始节点的路径)Dijkstra算法步骤:(1)初始化:顶点集S:节点A到自已的最短路径长度为0。只包含源点,即S={A}顶点集U:包含除A外的其他顶

随机推荐