【有营养的算法笔记】基础算法 —— 推导证明前缀和与差分2

简介: 【有营养的算法笔记】基础算法 —— 推导证明前缀和与差分

三、一维差分


其实博主觉得差分是一个很抽象的算法,我们可以构造差分数组算,同样的也可以通过另一种方式不构造数组求出结果。


至于为什么我会这么觉得,别急,我们慢慢来,先讲差分的思想再说~



1、算法推导


前面我们学了前缀和,现在又要学差分,它们之间有联系吗?


实际上可以简单推测一下,一个是求 ”和“ ,一个是求 ”差“ ,那 差分是不是就是前缀和的逆运算 ?答案是正确的,差分其实就是前缀和的一个反推。


对于差分算法我们一般得先 构造差分数组 ,假设现在有两个数组 a 和  b a 和\ b a和 b :


我们需要构造 b b b 为差分数组,使得 a a a 数组是 b b b 的前缀和数组 ,也就是说 a a a 中每一个数据,都是 b b b 中在包括这个位置之前所有的数据的和。这时 b b b 被称为 a a a 的差分。


所以 b b b 数组每个元素无非就是 a a a 数组的每一个元素与其前一个元素的差嘛!


为什么这么说?我们来算一下就知道了:

image.png


所以 差分数组       b 的构造方式如下


image.png

那么这里的 构造过程 实际上就是遍历一遍 a a a 数组,就可以构造出 b b b ,时间复杂度为 O ( N ) O(N) O(N) 。我们再看看代码怎么写:

for (int i = 1; i <= n; i++) {
        b[i] = a[i] - a[i - 1];
    }


讲完了构造,我们再看看差分这个算法为了解决什么问题:


差分算法是为了解决让 序列中某段区间 [ l , r ] [l, r] [l,r] 加上一个 常数 c c c 的问题。


放到平时,那么我们肯定是遍历一遍,然后把数据 c c c 加上,时间复杂度为 O ( N ) O(N) O(N) ,但是如果使用差分呢?


假设现在 a a a 是原数组,差分数组 b b b 已经求好了,此时要让 a a a 数组 [ l , r ] [l, r] [l,r] 区间内 + c +c +c,只需要让 b [ l ] + = c ,让 b [ r + 1 ] − = c \color{red}b[l] += c,让 b[r + 1] -=c b[l]+=c,让b[r+1]−=c 即可,时间复杂度为 O ( 1 ) O(1) O(1) 。


如下图:


17a63f00c480a5bd565b8dfb5a2f27ed.png


但是为什么这样就可以了,它的原理是什么?我们还得继续探究:


由于 a a a 数组是 b b b 数组的前缀和,所以让 b [ l ] + = c \color{red}b[l] += c b[l]+=c ,就会造成如下结果:


image.png


同理,对于 b[r+1]−=c 也可以推导,下面我就简写一下了:


image.png


由此,得证只要让 b [ l ] + = c ,让 b [ r + 1 ] − = c \color{red}b[l] += c,让 b[r + 1] -=c b[l]+=c,让b[r+1]−=c,就可以使 a a a 数组中 [ l . r ] [l. r] [l.r] 区间内元素加上 常数 c c c 。



2、代码实现


   高能预警,代码实现这块就是博主觉得比较抽象,但很神奇的地方。

   先上题目 ~


链接:797.差分


描述:


输入一个长度为 n n n 的整数序列。


接下来输入 m m m 个操作,每个操作包含三个整数 l , r , c l,r,c l,r,c 表示将序列中 [ l , r ] [l,r] [l,r] 之间的每个数加上 c c c 。


请你输出进行完所有操作后的序列。


输入格式:


第一行包含两个整数 n n n 和 m m m 。

第二行包含 n n n 个整数,表示整数序列。


接下来 m m m 行,每行包含三个整数 l , r , c l,r,c l,r,c 表示一个操作。

输出格式:


共一行,包含 n n n 个整数,表示最终序列。


数据范围:


   1 ≤ n , m ≤ 100000 1≤n,m≤100000 1≤n,m≤100000

   1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1≤l≤r≤n

   − 1000 ≤ c ≤ 1000 −1000≤c≤1000 −1000≤c≤1000

   − 1000 ≤ 整数序列中元素的值 ≤ 1000 −1000≤整数序列中元素的值≤1000 −1000≤整数序列中元素的值≤1000



输入样例

6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1

输出样例

3 4 5 3 4 2



代码1:

这段代码就是我们上方 算法推导的完全复刻 ,先构造差分矩阵 b b b ,再根据方法对区间进行操作。

对 b b b 数组完成计算之后,对齐求一下 前缀和 ,将数据存到 a a a 数组中。


435652b6594e95688c63150b181bcde5.png

代码2

但是其实 y 老师讲的其实不是这种方法,他用了更加巧妙的一个方式。



y 老师的思路,是将 a a a 数组的所有元素全部假定为 0 ,用了一个 insert 函数,不考虑构造,通过对小区间的插入,和对 [ l , r ] [l, r] [l,r] 区间的处理,直接完成了对区间 [ l , r ] \color{red}[l, r] [l,r] 的运算。


它的思路是 单个小区间,也可以说是相同区间 进行数据之间的增加,比如:

image.png

每个数据一开始看做 0 ,这样子就像对每个值进行 +c ,再进行之后 [ l , r ] [l, r] [l,r] 区间的操作,直接求出结果。


这样就忽略了 构建差分数组 ,直接从结果上求解,运用了 差分的特性:对差分数组求前缀和就算得原数组 ,从而直接求得结果。


先来看一眼代码:



f6fd67d30f0ffe94b5ef73efd6666f17.png


我第一眼看到这个代码我就觉得很惊艳,这是一个很巧妙的做法,但是过一会我就十分疑惑 :

如果我不从最终结果上来看,我就是要看 过程 呢?差分之前说过,第一步就要构建 差分数组 ,之后才能进行求解,但是这里没有构建是怎么算的?


可能是博主比较无聊,就想了好一会,看着代码没想明白,后来把这个过程推了一遍,发现,这一过程真的十分妙!


在上图中,我给出 红色 方框的部分既可以看做对小区间进行数据插入,又可以看做是在构建差分数组。


为什么这么说?下面我们进行一下推导证明:


之间我们推导过如何构造 差分矩阵 b 差分矩阵 b 差分矩阵b ,这里再提一下,这里是 关键 :


image.png


 

由于 b b b 是全局数组,所以一开始元素都是为 0 的。

所以 b 初始状态为:


image.png


注:这里第一行为 b b b 数组,第二行为 下标 ,由于 LaTeX 我用的还不是很熟练,所以还不能很好的控制对齐和缩进,加上标识这个就不对齐了,效果不太好,所以就省略了~大家看到这条之后委屈一下hh,我会尽量说明白的。


现在开始模拟这一过程:


   第一次循环,insert(1, 1, a[1]) ,对 [ 1 , 1 ] [1, 1] [1,1] 进行 a [ 1 ] a[1] a[1] 元素的插入,插入之后结果:


image.png


由于插入过程为:b[l] += c ,b[r + 1] -= c。而我们 l = r = a [ 1 ] l = r = a[1] l=r=a[1] ,所以这边就相当于对 b [ 1 ] + = a [ 1 ] b[1] += a[1] b[1]+=a[1],对 b [ 2 ] − = a [ 1 ] b[2] -= a[1] b[2]−=a[1] ,这里还不能看出什么,继续模拟:


   第二次循环,insert(2, 2, a[2]) ,对 [ 2 , 2 ] [2, 2] [2,2] 进行 a [ 2 ] a[2] a[2] 元素的插入,插入之后结果:

image.png


进行第二次插入后,对 b [ 2 ] + = a [ 2 ] b[2] += a[2] b[2]+=a[2] ,对 b [ 3 ] − = a [ 2 ] b[3] -= a[2] b[3]−=a[2],我们再回顾一下刚刚提到的 如何构造差分数组 b b b 有些不可思议的发现?先别急,我们把这一过程推完再下结论。


   博主之前已经推过一遍了,这次我们直接跳到最后一次:


image.png

最后这一循环结束的时候的结果是这样的,实际上,这就已经完成了对差分数组的构建 。


再回头看看我们上方画出的重点:如何构造差分矩阵 b b b,现在其实就都可以想通了,实际上这一过程就是变相的构造出来了 差分数组。


虽然从表面上是对 小区间插入数据 ,但实际上就是在 构建 ,hhh。


其实就单单从结果上来看还是很简单的,但是往里看点,可能就会陷进去,博主就是其中之一,但是博主比较轴,还是给它推了一下,满足一下自己的好奇心,也是为了下面讲二维做一下铺垫。

   到这里,我只能说 y 老师 nb !




四、二维差分


1、算法推导


二维差分是一维差分的一个升级,二维差分,可以对二维数组中某块区域进行 + − +- +− 变换。

假定 a a a 为原二维数组, b b b 为二维差分数组,此时 b b b 为 a a a 的差分数组, a a a 为 b b b 的前缀和数组。


这里其实对于 二维差分数组的构建也是十分有意思的,我们待会再讲,先看如何让二维数组的子矩阵中每个元素的值 + c +c +c :


下图为 二维差分数组 b b b :

0ab34fba3f1ee41e22fa364e8726de2e.png


为了更好的理解这个过程,在讲之间,我再把  a 数组对应分块画出来:

0ea5bf4f57ef17affb4d7085bef4913c.png

为了使得 a 数组中 黄色区域 +c ,一共需要对 差分数组 b b b 做如下操作:

b[x1][y1]+=c:由于 a a a 是 b b b 的前缀和数组,所以只要对 b[x1][y1]+=c 就会使得图 ( 1 ) (1) (1) 蓝色面积的元素 + c +c +c,大概的推导我们在一维差分那边推导过,这边就不多赘述了。

b[x2+1][y1]−=c:使图 (2) 紫色面积的元素 − c

b[x1][y2+1]−=c:使图  (3) 红色面积的元素 − c

b[x2+1][y2+1]+=c :使图 (4) 绿色面积的元素 + c


至于为什么要让绿色面积加上 c c c 是因为,紫色面积和红色面积重叠部分为绿色面积,绿色面积被减了两次,得补上一次 c c c。


这里的思想其实和一维差分是很相似的,我们可以把一维差分的这些操作看做长度,即区间上的数据改变;


而这里则是通过面积,想要让子矩阵 + c +c +c 那么就让整体减去部分,得到子矩阵的区域,而上方我们减去的紫色和红色区域其实就是和一维差分中的多余部分是相似的。只不过原来在一维的操作扩展到了二维。


   到这我们就把具体操作讲完了,接下来就该讲 如何构造二维差分数组 了:


其实讲一维差分的时候,把 y 老师的做法特意抽出来推导一遍就是为了这里。


上方我们证明了,其实在对小区间进行插入的过程中,就完成了对差分数组的构造,那么在这边我们对于二维差分数据的构建是比较麻烦的,这时使用y 老师的办法就显得 非常简单 :


同样的,我们把上方 操作 的代码 封装成 insert 函数 :

void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
}


将 原始数组 a a a 的元素都假想为 0 ,然后把原始数组 a a a 的每个元素想象成一个一个的 小矩阵 ,然后对每个小矩阵进行插入操作,这一过程其实就已经构建出 二维差分数组 :


3c7e11f6badbd75de03799652e00a67f.png

最后再对需要求的 子矩阵 进行操作就可以求得结果。


那么能不能用构造的方式,把差分数组构建一下,答案是可以的。


其实对于二维差分数组的构建我觉得还是挺麻烦的,所以博主用的是一个 反推 的方式:

之前讲过, a a a 数组是 b b b 数组的前缀和,对 b b b 数组求前缀和就可以得到 a a a 数组的值,公式如下:


a[i][j]=a[i1][j]+a[i][j1]a[i1][j1]+b[i][j]



这是我们按照前缀和公式的角度去看的,现在 b [ i ] [ j ] b[i][j] b[i][j] 实际上就是求前缀和时,加上的小方块的面积,那么算出这个就只要交换一下等式两边的数据就可以:

b[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1];


这就非常简单地得到了 构建差分数组 的公式,比干巴巴地去想如何构建清晰很多。



2、代码实现


链接798. 差分矩阵


描述:


输入一个 n n n 行 m m m 列的整数矩阵,再输入 q q q 个操作,每个操作包含五个整数 x 1 , y 1 , x 2 , y 2 , c x1,y1,x2,y2,c x1,y1,x2,y2,c 其中 ( x 1 , y 1 ) (x1,y1) (x1,y1) 和 ( x 2 , y 2 ) (x2,y2) (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。


每个操作都要将选中的子矩阵中的每个元素的值加上 c c c 。


请你将进行完所有操作后的矩阵输出。


输入格式:


第一行包含整数 n , m , q n,m,q n,m,q。

接下来 n n n 行,每行包含 m m m 个整数,表示整数矩阵。

接下来 q q q 行,每行包含 5 5 5 个整数 x 1 , y 1 , x 2 , y 2 , c x1,y1,x2,y2,c x1,y1,x2,y2,c 表示一个操作。

输出格式:


共 n n n 行,每行 m m m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围:


   1 ≤ n , m ≤ 1000 1≤n,m≤1000 1≤n,m≤1000

   1 ≤ q ≤ 100000 1≤q≤100000 1≤q≤100000

   1 ≤ x 1 ≤ x 2 ≤ n 1≤x1≤x2≤n 1≤x1≤x2≤n

   1 ≤ y 1 ≤ y 2 ≤ m 1≤y1≤y2≤m 1≤y1≤y2≤m

   − 1000 ≤ c ≤ 1000 −1000≤c≤1000 −1000≤c≤1000

   − 1000 ≤ 矩阵内元素的值 ≤ 1000 −1000≤矩阵内元素的值≤1000 −1000≤矩阵内元素的值≤1000



输入样例

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1


输出样例

2 3 4 1
4 3 4 1
2 2 2 2


代码1

常规构造,最终结果将求得结果放入         a 中。

3149a172f49110cf09e54a20ada7204f.png


代码2

y 老师用的插入的方式,结果直接放在          b 中。

869c40270c34d0c2f128b7fb18b2e617.png

目录
打赏
0
0
0
0
9
分享
相关文章
强化学习:时间差分(TD)(SARSA算法和Q-Learning算法)(看不懂算我输专栏)——手把手教你入门强化学习(六)
本文介绍了时间差分法(TD)中的两种经典算法:SARSA和Q-Learning。二者均为无模型强化学习方法,通过与环境交互估算动作价值函数。SARSA是On-Policy算法,采用ε-greedy策略进行动作选择和评估;而Q-Learning为Off-Policy算法,评估时选取下一状态中估值最大的动作。相比动态规划和蒙特卡洛方法,TD算法结合了自举更新与样本更新的优势,实现边行动边学习。文章通过生动的例子解释了两者的差异,并提供了伪代码帮助理解。
68 2
|
6月前
|
HashMap扩容时的rehash方法中(e.hash & oldCap) == 0算法推导
HashMap在扩容时,会创建一个新数组,并将旧数组中的数据迁移过去。通过(e.hash & oldCap)是否等于0,数据被巧妙地分为两类:一类保持原有索引位置,另一类索引位置增加旧数组长度。此过程确保了数据均匀分布,提高了查询效率。
104 2
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
124 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
人脸识别笔记(一):通过yuface调包(参数量54K更快更小更准的算法) 来实现人脸识别
本文介绍了YuNet系列人脸检测算法的优化和使用,包括YuNet-s和YuNet-n,以及通过yuface库和onnx在不同场景下实现人脸检测的方法。
194 1
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
60 0
|
6月前
|
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
60 0
基于GA遗传算法的斜拉桥静载试验车辆最优布载matlab仿真
本程序基于遗传算法(GA)实现斜拉桥静载试验车辆最优布载的MATLAB仿真,旨在自动化确定车辆位置以满足加载效率ηq(0.95≤ηq≤1.05)的要求,目标是使ηq尽量接近1,同时减少加载车辆数量和布载耗时。程序通过迭代优化计算车辆位置、方向、类型及占用车道等参数,并展示适应度值收敛过程。测试版本为MATLAB2022A,包含核心代码与运行结果展示。优化模型综合考虑车辆总重量、间距及桥梁允许载荷密度等约束条件,确保布载方案科学合理。
基于免疫算法的最优物流仓储点选址方案MATLAB仿真
本程序基于免疫算法实现物流仓储点选址优化,并通过MATLAB 2022A仿真展示结果。核心代码包括收敛曲线绘制、最优派送路线规划及可视化。算法模拟生物免疫系统,通过多样性生成、亲和力评价、选择、克隆、变异和抑制机制,高效搜索最优解。解决了物流仓储点选址这一复杂多目标优化问题,显著提升物流效率与服务质量。附完整无水印运行结果图示。
基于免疫算法的最优物流仓储点选址方案MATLAB仿真
基于GA遗传优化TCN时间卷积神经网络时间序列预测算法matlab仿真
本内容介绍了一种基于遗传算法优化的时间卷积神经网络(TCN)用于时间序列预测的方法。算法运行于 Matlab2022a,完整程序无水印,附带核心代码、中文注释及操作视频。TCN通过因果卷积层与残差连接学习时间序列复杂特征,但其性能依赖超参数设置。遗传算法通过对种群迭代优化,确定最佳超参数组合,提升预测精度。此方法适用于金融、气象等领域,实现更准确可靠的未来趋势预测。
基于GA遗传优化TCN-LSTM时间卷积神经网络时间序列预测算法matlab仿真
本项目基于MATLAB 2022a实现了一种结合遗传算法(GA)优化的时间卷积神经网络(TCN)时间序列预测算法。通过GA全局搜索能力优化TCN超参数(如卷积核大小、层数等),显著提升模型性能,优于传统GA遗传优化TCN方法。项目提供完整代码(含详细中文注释)及操作视频,运行后无水印效果预览。 核心内容包括:1) 时间序列预测理论概述;2) TCN结构(因果卷积层与残差连接);3) GA优化流程(染色体编码、适应度评估等)。最终模型在金融、气象等领域具备广泛应用价值,可实现更精准可靠的预测结果。

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等