【C++算法图解专栏】一篇文章带你掌握差分算法

news2024/11/24 3:07:33

✍个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343
📣专栏定位:为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解,也欢迎大佬们一起交流~
📚专栏地址:https://blog.csdn.net/Newin2020/article/details/126445229
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
🎏唠叨唠叨:在这个专栏里我将会整理 PAT 甲级的真题题解,并将他们进行分类,方便大家参考。

差分

前面我们讲到了前缀和算法,这一讲我们来看看前缀和的逆运算即差分算法是什么,在有些题中需要我们对一个区间上的所有数进行加减操作,如果通过循环一个个加减时间复杂度会很高,这时差分算法就派上用场了,下面我们来看看差分是如何解决这类问题的,并且会进行小小的扩展,延伸到差分矩阵问题的解决。

Tips:不用被差分这么名字所吓到,其实真正学起来并不会特别难理解,相信你一定能快速掌握~

原理

假设给定一个原数组 a,那么差分数组 b 的存在就是使得如下公式成立:

a [ i ] = b [ 1 ] + b [ 2 ] + . . . + b [ i ] a[i]=b[1]+b[2]+...+b[i] a[i]=b[1]+b[2]+...+b[i]

而上面的公式,是由差分数组 b 推导而来:

b [ 1 ] = a [ 1 ] b[1]=a[1] b[1]=a[1]

b [ 2 ] = a [ 2 ] − a [ 1 ] b[2]=a[2]-a[1] b[2]=a[2]a[1]

b [ 3 ] = a [ 3 ] − a [ 2 ] b[3]=a[3]-a[2] b[3]=a[3]a[2]

. . . ... ...

b [ n ] = a [ n ] − a [ n − 1 ] b[n]=a[n]-a[n-1] b[n]=a[n]a[n1]

将上面公式两两相加,消除完后就可以得到 a[i] 的公式了。

因此我们称 ab 的前缀和,而 ba 的差分。

差分

现在我们来看如何将差分数组模拟出来,现在给定一个区间 [l,r],我们希望在这个区间上面的每个数都加上一个 c,如果直接循环添加每个数时间复杂度会很大。而利用差分加前缀和的方法,就可以使效率提高很多,至于如何操作直接上图解,首先初始化一个差分数组 b 并且数组中的元素一开始都为 0

现在执行上述操作,假设在 [3,6] 这个区间上的每个数都加上 1,则可以在下标为l 的地方加上 c,在下标为 r+1 的地方减去 c,操作后结果如下:

这时候我们再对数组 b 计算前缀和数组 a 就可以得到一个非常奇妙的结果,居然前缀和数组就是最终我们要得到的数组,这其实就是利用了上面的公式。

我们再执行一个操作,在区间 [1,4] 上的每个数都减去 1,其实就和上面操作一样,只是在下标 l 处加上了一个负数,在下标 r 处减去一个负数。

接着来看一道模板题,就是对区间上的数进行加减操作。

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

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

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

输入格式

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

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

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

输出格式

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

数据范围

1≤n,m≤100000,
1≤l≤r≤n1,
−1000≤c≤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

可以发现,和我们上面讲的例子一模一样,只是这里要多一个初始化操作,上面的图解我们将 b 中的元素都初始化为 0 。而在这道题中预先给定了一个数组,需要我们先将这个数组初始化到 b 上,例如给定第一个数为 1,则在下标 l=1r+1=2 处分别加上 1-1;第二个数为 2 ,则在下标 l=2r+1=3 处分别加上 2-2,以此类推。

初始化之后,再进行区间的加减操作,这也和我们上面一样,直接来看代码。

#include <bits/stdc++.h>
using namespace std;

int n, m, a[100005], b[100005];

//差分操作
void Add(int c, int l, int r) {
	b[l] += c;
	b[r + 1] -= c;
}

int main() {
	scanf("%d %d", &n, &m);
    //初始化数组b
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		Add(a[i], i, i);	
	}
    //进行区间加减操作
	while (m--) {
		int l, r, c;
		scanf("%d%d%d", &l, &r, &c);
		Add(c, l, r);
	}
    //通过计算b的前缀和还原出目标
	for (int i = 1; i <= n; i++) {
		b[i] += b[i - 1];
		printf("%d ", b[i]);
	}
	return 0;
}

差分矩阵

有前缀和矩阵,自然就有差分矩阵,不过做法和一维的差分十分类似,有前面的铺垫再来看这个就不难理解了,还是通过例子来讲,现在假设有一个 3×4 的矩阵,而差分矩阵 b 中的元素全部初始化为 0

现在假设给定两个坐标 (1,1)(2,2) 分别表示我要指定的子矩阵的左上角和右下角,然后我想要将这个子矩阵中的所有元素都加上 1。那么我们就需要在四个地方进行修改(下面中的每条公式修改范围都对应了其下图中红色区域):

  • b [ x 1 ] [ y 1 ] + = c b[x1][y1]+=c b[x1][y1]+=c,相当于包括 (x1,y1) 在内的右下区域都加上了 c

  • b [ x 2 + 1 ] [ y 1 ] − = c b[x2+1][y1]-=c b[x2+1][y1]=c,相当于包括 (x2+1,y1) 在内的右下区域都减去了 c

  • b [ x 1 ] [ y 2 + 1 ] − = c b[x1][y2+1]-=c b[x1][y2+1]=c,相当于包括 (x2,y1+1) 在内的右下区域都减去了 c

  • b [ x 2 + 1 ] [ y 2 + 1 ] + = c b[x2+1][y2+1]+=c b[x2+1][y2+1]+=c,相当于包括 (x1+1,y1+1) 在内的右下区域都加上了 c

先别着急,我们来看看执行后的效果如何。

可以惊讶的发现,计算完子矩阵前缀和后,居然就是我们想要得到的矩阵,这其实就和子矩阵前缀和运算的性质有关,其计算公式如下:

b [ i ] [ j ] + = b [ i − 1 ] [ j ] + b [ i ] [ j − 1 ] − b [ i − 1 ] [ j − 1 ] b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1] b[i][j]+=b[i1][j]+b[i][j1]b[i1][j1]

可以发现相对于正常的子矩阵前缀和运算少加了一个 a[i][j],这是因为差分一开始已经进行了初始化操作,每个位置上初始的值都已经加到差分数组 b 中了,我们只用管修改操作即可。

另外,这部分差分的操作和前缀和的操作和其实有点类似,都需要对重复的部分进行操作。前缀和是因为多加了一次重复区域而要减去重复的部分,而差分则是多减了一次重复区域而加上重复的部分,大家可以将上述公式带入到图中矩阵进行验证,发现能够完全对应的上。

对前缀和二维运算不太熟悉或没有接触过的小伙伴可以跳转到我之前的文章,传送门如下:

【C++算法图解专栏】一篇文章带你掌握前缀和算法(一维+二维)

讲完了上述原理,下面这道模板题都没有问题啦!

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

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

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

输入格式

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

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

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

输出格式

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

数据范围

1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤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

可以发现和一维的差分很类似,本地都给了初始的数组,需要我们先对差分数组 b 进行初始化,比如 (1,1) 的下标处初始值为 1,将传入的左上角坐标和右上角坐标都设置相同的坐标 (1,1),这样就相当于在 (1,1) 处加上了值 1,其它情况类似。

初始化完后,再进行区间的加减操作,代码如下:

#include<iostream>
using namespace std;

int n, m, q, a[1010][1010], b[1010][1010];

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

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]);
            insert(i, j, i, j, a[i][j]);
        }
    }
    //对区间进行加减
    while (q--)
    {
        int x1, x2, y1, y2, c;
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        insert(x1, y1, x2, y2, c);
    }
    //计算前缀和
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            //子矩阵前缀和计算要减去重复的部分
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    return 0;
}

总结

恭喜您成功点亮差分算法技能点!

在平时做题的过程中,差分和前缀和两个算法经常会一起出现,前缀和可以没有差分,但差分往往不能没有前缀和,因为差分数组只是记录了区间的操作,还需要前缀和将数组还原出来。

在一开始学差分的时候,往往会被这个名字给吓退,但其实理解起来没有这么难,在平时算法题中一般也只会是一个零件,比如一些题中需要对区间上的数进行加减操作,这时就要想到可以用差分来解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hadoop.fs.FileSystem.get导致OOM的原因和解决方案

问题描述 在调用HDFS获取文件系统的get接口时&#xff0c;指定用户可能会导致OOM问题&#xff0c;示例代码如下&#xff1a; FileSystem fileSystem FileSystem.get(uri, conf, "hadoopuser");问题溯源 该方法源码&#xff1a; 在有缓存的情况下将从Cache中取&a…

Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景

概述 本文续自:Android 11 的状态栏的隐藏 PS 本文虽然同属于SystemUI, 但目前并 没有 打算整理成专橍或撰写一个系列的想法. 仅仅为了记录一些过程, 留下那些容易被遗忘的点滴. 开始下拉时状态栏图标被隐藏 状态橍的图标在用户开始触摸(ACTION_DOWN)后, 会开始展开, 显示扩展…

答题小程序题目批量导入模板以及题库文本格式规范

近期又接到了一个知识竞赛的需求&#xff0c;在开发答题小程序的过程中&#xff0c;遇到了不少难题&#xff0c;但是都一一克服了。凭借多年的答题小程序开发经验&#xff0c;我总结了一下题目批量导入题库文本格式规范。一、答题小程序题目批量导入模板小程序【答题小博士】二…

《数字经济全景白皮书》后疫情时代数字化驱动增长洞察之赛道篇

易观分析&#xff1a;《数字经济全景白皮书》浓缩了易观分析对于数字经济各行业经验和数据的积累&#xff0c;并结合数字时代企业的实际业务和未来面临的挑战&#xff0c;以及数字技术的创新突破等因素&#xff0c;最终从数字经济发展大势以及各领域案例入手&#xff0c;帮助企…

面试之 Python 框架 Flask、Django、DRF

Django、flask、tornado 框架的比较 ★★★★★ Django&#xff1a;大而全的框架。它的内部组件比较多&#xff0c;如 ORM、Admin、中间件、Form、ModelForm、Session、缓存、信号、CSRF等&#xff0c;功能也都很完善。 flask&#xff1a;微型框架&#xff0c;内部组件就比较少…

JavaScript中的事件对象、事件对象的属性

一、什么是事件对象​ 1&#xff09;、 事件对象 就是保存着事件相关信息的对象。当事件发生时&#xff0c;会自动产生事件对象&#xff08;不需要new&#xff09;&#xff0c;事件对象中包含着&#xff1a;事件源&#xff08;发生事件的dom元素&#xff09;&#xff0c;点击是…

全网最详细的mybatis plus 条件构造器queryWrapper学习,比如and(),eq(),or(),like()等方法以及分页操作

文章目录1. 引言2. 结构关系3. 环境配置3.1 引入jar包3.2 创建数据源3.2 创建User实体类3.4 创建UserMapper类3.5 创建UserService类4. 操作演示5. 注意事项1. 引言 mybatis大家都有使用过&#xff0c;既面向对象又灵活可配。不友好的地方是&#xff0c;会随着使用出现大量xml…

一篇文章带你读懂AVL树

目录 AVL树节点的定义 AVL树的插入 AVL树的旋转 1. 新节点插入较高左子树的左侧---左左&#xff1a;右单旋 2.新节点插入较高右子树的右侧---右右&#xff1a;左单旋 3. 新节点插入较高左子树的右侧---左右&#xff1a;先左单旋再右单旋 4. 新节点插入较高右子树的左侧-…

人工智能自然语言处理—PageRank算法和TextRank算法详解

人工智能自然语言处理—PageRank算法和TextRank算法详解 一、PageRank算法 PageRank算法最初被用作互联网页面重要性的计算方法。它由佩奇和布林于1996年提出&#xff0c;并被用于谷歌搜索引擎的页面排名。事实上&#xff0c;PageRank可以在任何有向图上定义&#xff0c;然后…

公司企业如何设计微信小程序?

​很多公司企业在制作小程序的时候都会考虑一个事情&#xff0c;就是如何设计微信小程序。有些公司企业希望把小程序设计得非常炫酷、抓人眼球。那么问题是&#xff1a;公司企业微信小程序的设计是否做得越酷炫、越抓人眼球就越好呢&#xff1f; 答案&#xff1a;非也&#xf…

基于SIFT的图像Matlab拼接教程

前言图像拼接技术&#xff0c;将普通图像或视频图像进行无缝拼接&#xff0c;得到超宽视角甚至360度的全景图&#xff0c;这样就可以用普通数码相机实现场面宏大的景物拍摄。利用计算机进行匹配&#xff0c;将多幅具有重叠关系的图像拼合成为一幅具有更大视野范围的图像&#x…

(一)Spring源码解析:容器的基本实现

一、Spring的整体架构 Spring的整体架构图如下所示&#xff1a; 二、容器的基本实现 2.1> 核心类介绍 2.1.1> DefaultListableBeanFactory DefaultListableBeanFactory是整个bean加载的核心部分&#xff0c;是Spring注册及加载bean的默认实现。 XmlBeanFactory集成自…

【FLASH存储器系列十四】固态硬盘结构和FTL初探

固态硬盘是一种典型的nand flash产品应用。与传统硬盘相化&#xff0c;固态硬盘内部没有移动的机械磁头&#xff0c;而是由固态电子存储芯片&#xff08;闪存芯片&#xff09;阵列级联组成&#xff0c;下图给出了固态硬盘的内部组成。现阶段&#xff0c;几乎所有基于闪存的固态…

ASP.NET Core+Element+SQL Server开发校园图书管理系统(三)

随着技术的进步&#xff0c;跨平台开发已经成为了标配&#xff0c;在此大背景下&#xff0c;ASP.NET Core也应运而生。本文主要基于ASP.NET CoreElementSql Server开发一个校园图书管理系统为例&#xff0c;简述基于MVC三层架构开发的常见知识点&#xff0c;前两篇文章简单介绍…

Nvidia深度学习环境安装

深度学习大型模型训练和部署&#xff0c;需要使用GPU&#xff0c;使用Pytorch、Tensorflow等深度学习框架之前需要安装驱动环境,本文系统环境&#xff1a;ubuntu22.04系统&#xff0c;四张3090显卡安装显卡驱动下载&#xff1a;选择显卡类型&#xff0c;下载驱动驱动下载路径&a…

Wireshark解析协议不匹配

Wireshark解析协议不匹配 1、问题 现有TLS/SSL over TCP的客户端、服务端相互通信&#xff0c;其中&#xff0c;服务端监听TCP端口6000。 使用tcpdump抓包6000端口&#xff0c;生成pcap文件6000.pcap&#xff1a; 使用Wireshark打开6000.pcap&#xff0c;显示如下&#xff1…

Hive(番外):Hive可视化工具IntelliJ IDEA

1 Hive CLI、Beeline CLI Hive自带的命令行客户端 优点&#xff1a;不需要额外安装 缺点&#xff1a;编写SQL环境恶劣&#xff0c;无有效提示&#xff0c;无语法高亮&#xff0c;误操作几率高 2 文本编辑器 Sublime、Emacs 、EditPlus、UltraEdit、Visual Studio Code等 有…

基于Seam Carving实现图像的重定位 附完整代码

相比于算法目标的复杂&#xff0c;算法步骤却异常的简单&#xff0c;下面具体介绍利用 SeamCarving 算法进行图像剪裁的步骤&#xff1a;1.计算图像中每个像素的“重要程度”&#xff08;能量&#xff09;&#xff0c;生成能量图。在绝大多数情况下&#xff0c;我们可以做出如下…

【string 类的使用方法(总结)】

1. 为什么学习string类&#xff1f; C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需要…

采用NVIDIA Jetson Orin NX 系统的视觉边缘计算机

边缘计算机采用NVIDIA Jetson Orin NX模块化系统和高带宽图像采集卡&#xff0c;用于实时图像采集计算和人工智能处理。虹科的合作伙伴Gidel是一家专注于高速图像采集和处理的以色列科技公司&#xff0c;今天宣布新的NVIDIA Jetson Orin NX™ 16GB模块化系统(SoM)将被添加到Gid…