一维差分算法篇:高效处理区间加减

news2025/2/23 22:45:57

在这里插入图片描述

那么在正式介绍我们的一维差分的原理前,我们先来看一下一维差分所应用的一个场景,那么假设我们现在有一个区间为[L,R]的一个数组,那么我要在这个数组中的某个子区间比如[i,m] (L<=i<=m<=R)进行一个加k值或者减去k值的一个操作,那么我们常规的暴力方法就是我们直接遍历一遍该数组的子区间每个位置加k或者每个位置减k,那么一旦该操作多了,那么对数组的遍历就过于频繁,那么我们能不能用一个较小的代价来完成这每个区间的加k值或者减k值的所有操作而不是每次都去遍历一遍数组呢,那么我们的一维差分就能做到这一点。

1.一维差分原理

那么我们现在有一个区间为[L,R]的一个数组,那么我们要在其中的任意的子区间加k或者减k,那么这里我们目标是想得到我们这个数组从原始状态经过该操作后也就是在任意子区间加减k值后的一个最终状态,那么我们对这个数组的任意子区间的加减操作我们可以理解为我们要在数组原状态下所叠加的状态,那么如果我们能够知道我们数组各种叠加状态后的综合得到的一个最终的叠加状态,那么我们数组在此基础上只需要叠加一个综合的叠加状态那么就可以得到最终状态了。

这里我们还是举一个例子来解释一下我刚才的一个观点,那么假设我们有长度为10的一个原数组[1,2,3,4,5,6,7,8,9,10],那么这里假设我们对这个[0,9]区间的数组中的[0,4]区域处整体减去3,在[0,2]区域处整体加2,最后在[3,5]区域处整体加1,那么我们要知道在这三个操作下我们数组最终的状态的各个位置的值是怎样的

那么我们这里可以把我们数组最开始的形态:[1,2,3,4,5,6,7,8,9]视作初始状态,而我们这三次操作比如在[0,4]区域处整体减去3,视作我们要叠加的状态,那么这里我们如果要得到我们的目标数组,我们肯定是分别叠加三次要叠加的状态,比如在[0,4]减3,然后再[0,2]区域加二,最后在[3,5]区域加1,最终得到我们数组的最终状态也就是[0,1,2,2,3,5,6,7,8,9],那么现在我们希望就是只用叠加一次状态就得到我们的最终状态,那么这里我们只用叠加一次,那么也就意味着我们要知道这三次叠加状态的一个综合效果。

那么这里得到这里所谓的综合得到的叠加状态,就是通过建立一个差分数组diff,该数组的下标就对应我们原始数组的相应位置,那么其中的每个位置的值则表示我们原始数组中对应位置最终该叠加的一个状态也就是[-1,-1,-1,-2,-2,0,0,0,0,0],那么我们原始数组每个位置加上对应的值就能够得到我们最终状态也就是[0,1,2,2,3,5,6,7,8,9],也就只需要遍历一遍数组即可。

所以现在的疑问就转变为了如何加载这个差分数组diff,那么这里我们假设我们要在区间[L,R]中的子区间[i,m]处加v,那么这里我们需要一个与我们原始数组长度对应的一个差分数组,然后将其初始化每个位置的大小都为0,然后我们这里只需要在下标为i位置处加v,然后再m+1处加-v,然后对该差分数组加载对应的一份前缀和数组,那么我们就可以把前缀和数组中区间[i,m]的整体的值给全部刷成v,那么我们对于区间[i,m]的每个位置上的数要叠加的状态就是该区间的每个数加v,那么我们通过这两步就能得到区间[i,m]的叠加状态。

看到这里你想必一定有两个疑问,第一问是想问我为什么要在i位置处加v,在m+1位置处要减去v?第二问则是为什么加工一遍前缀和就能够得到该操作下的叠加状态?

这里我们知道我们的叠加状态就是对整个区间统一进行加或者减去定值k,那么这里我们要得到该状态所对应的差分数组的话,那么差分数组中对应[i,m]的值一定全部是k或者-k,那么这里我们如何将差分数组中的[i,m]全部设置为k或者-k呢,当然肯定不是通过遍历,而这时候前缀和派上用场了,因为我们前缀和的计算公式是sum[i]=sum[i-1]+arr[i],那么我们发现如果我们在i位置处加一个v,那么根据前缀和的公式:sum[i]+v=sum[i-1]+(arr[i]+v),那么此时i位置加v之前对于第i+1位置的前缀和是:sum[i+1]=sum[i]+arr[i+1],但是我们由于i位置加了一个v,那么sum[i]此时的值是sum[i]+v,那么对于sum[i+1]来说,由于sum[i+1]=sum[i]+v+arr[i+1],而现在sum[i]的值变成了sum[i]+v,所以sum[i+1]的值就变成了sum[i+1]+v,那么我们可以依次类推:
在这里插入图片描述

那么这里我其实就可以观察到我们在i位置处加v,然后加工一遍前缀和,我们包括i位置在内以及之后的每一个前缀和都会加v,那么这个加v的效果会持续到包括i位置在内的右侧的所有区间,那么根据我们上面的式子,由于我们差分数组的每个数初始化为0,那么也就意味着我们在sum[i]的前缀和是0,那么我们如果不加v的话,那么我们这个差分数组加工得到的前缀和数组的每个位置都是0,但是由于我们这里在i位置加了v,那么我们知道我们在包括i位置之后的右侧区间全部会刷成v,但是我们只要[i,m]处刷成v,那么我们得到m位置之后抵消这个加v的效果,那么我们就在m+1位置从设置-v,那么我们m+1以及右侧的位置都会有一个加-v的效果,那么与前面的抵消了,所以这就是为什么我们在i位置处设置v,m+1位置处设置-v,然后加工一遍前缀和的原因。
在这里插入图片描述

那么我们如果在[i,m]区间处减去v的话,只要你看懂我们上面的原理,那么就不用我再去讲解赘述了,所以我们对于多次这样的操作,比如在某个区间[i,m]加v或者减去v,那么我们就根据上文讲的原理,在对应位置处i位置加或者减去v,然后再相应的m+1位置处减或者加v,然后加工一遍前缀和,就能得到这所有操作综合的叠加状态了


那么这里其实代码模版就很简单,就是一个差分数组和对应加工的前缀和数组,只不过这里在实现的时候注意边界问题,因为我们如果在比如[i,R]区间上加v或者减v,R是我们这个数组的右边界,那么我们的差分数组如果跟原数组长度一样的话,那么根据刚才的原理,我们会在R+1位置处减v或者加v,那么为了不越界的话,我们会加上一些条件判断,但是如果你不想讨论边界的话,你的差分数组的长度就可以比原数组大一个单位,也就是原数组的长度是n,那么你的差分数组1长度就是n+1,然后按照刚才的思路去在相应的位置设置值,最后只需要加工一遍前缀和即可。

公式:

在[i,m]统一加v:diff[i]+v diff[m+1]-v

在[i.m]统一减v:diff[i]-v diff[m+1]+v

代码实现:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> arr(10) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    vector<int> diff(arr.size() + 1, 0); // 差分数组长度应为原数组长度+1

    // 假设要在[0,3]区间加3,在[2,4]区间减2
    diff[0] += 3;
    diff[4] -= 3; // 注意这里是4,因为区间是[0,3],所以结束位置+1
    diff[2] -= 2;
    diff[5] += 2; // 同样,区间是[2,4],所以结束位置+1

    vector<int> diffsum(arr.size()+1, 0); 

    for (int i = 1; i < arr.size(); i++) {
        diffsum[i] = diffsum[i - 1] + diff[i-1];
    }
    return 0;
}

那么很多人看完这个一维差分,感觉差分不是很高效,我们加载一遍前缀和意味着要遍历一遍数组,那么复杂度是o(N),那么如果说我们只是对区间[L,R]的数组中某个子区间统一的加或减去v,那么根绝还不如我直接遍历这个子区间高效,但是我们差分数组在面对这样的场景,比如你这里要对数组的子区间进行100次甚至1000多次的加v或者减v操作,如果这个数组还特别长的话,那么此时长分就只需要遍历一遍数组即可,所以此时差分的优秀就体现出了

2.等差数列差分

那么我们这里有一个特殊的差分也就是等差数列差分,那么我们这里假设要在区间[i,m]中叠加的状态是一个等差数列,那么该等差数列首项是s,公差是d,那么i位置是s,往后依次是s+d,s+2d,所以最终的叠加状态:

[s,s+d,s+2d,s+3d,…,s+(i-m)*d]

那么这里我们要得到该叠加状态其实我们加载一遍前缀和是不够的,因为一边前缀和只能将区间刷成一个统一的值比如s比如d,那么这里一遍不够,那么也就意味着我们要加载两边前缀和,那么这里我们要得到我们加载两边前缀和数组的初始差分数组的形态,那么我们就采取逆推

我们这里先推最终叠加状态的上一级状态也就是中间状态,那么我们这里[i.m]每个位置都有首项s,那么这里我们的i位置就得加上s,然后m+1位置处减去s,那么这里由于我们这里i+1到n处的d是依次递增的,那么我们这里i+1到m处肯定都有一个d值,这样加载前缀和,前面的加d的效果到后面一个位置,那么在加上后面本身有一个d,那么该位置就得到2d,那么在往后传递,就能做到递增,但是要在m之后的区间抵消,那么我们就得在m+1位置处减去(i-m)*d

那么我们要得到中间状态,也就是[s,d,d,d,d,d,d,…,-(s+(i-m)*d)]那么我们则需要在l位置设置为s,l+1位置处设置为d-s,然后m+1位置处设置为-(s+(i-m))*d),然后我们加工两遍前缀和数组即可

代码实现:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n = 10; // 数组长度
    int i = 2;  // 区间起始位置
    int m = 5;  // 区间结束位置
    int s = 3;  // 等差数列首项
    int d = 2;  // 等差数列公差

    vector<int> arr(n, 0); // 原始数组
    vector<int> diff(n + 1, 0); // 差分数组

    // 初始化差分数组
    diff[i] += s;
    if (i + 1 <= m) {
        diff[i + 1] += -s + d;
    }
    if (m + 1 < n) {
        diff[m + 1] -= s + (m - i) * d;
    }

    // 第一次前缀和,得到中间状态
    vector<int> mid(n, 0);
    diff[0]=0
    for (int j = 0; j < n; j++) {
        mid[j] = mid[j] + diff[j];
    }

    // 第二次前缀和,得到最终状态
    vector<int> result(n, 0);
    result[0] = mid[0];
    for (int j = 1; j < n; j++) {
        result[j] = result[j - 1] + mid[j];
    }
    return 0;
}

3.结语

那么我们差分是我们处理区间加减操作的一个非常常用高效的一个算法,那么这里的差分我们是一维差分,那么既然都说了是一维差分,那么必然就有二维差分,那么我们了解了一维差分的原理之后,那么其实二维差分我们也大概知道它的用途,那么就是针对于二维数组某个区域的统一的加减问题,那么我会在之后的文章中会讲解我们的二维差分以及二维差分需要用到的二维前缀和,那么希望本文能够让你有所收获,我会持续更新,也希望你多多关注与支持,你的支持就是我最大的动力!
在这里插入图片描述

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

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

相关文章

【C++】基础入门(详解)

&#x1f31f; Hello&#xff0c;我是egoist2023&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; 目录 输入&输出 缺省参数(默认参数) 函数重载 引用 概念及定义 特性及使用 const引用 与指针的关系 内联inline和nullptr in…

【快速入门】Unity 常用组件(功能块)

欢迎关注 、订阅专栏 【unity 新手教程】谢谢你的支持&#xff01;&#x1f49c;&#x1f49c; 文章目录 Unity 常用组件&#xff08;功能块&#xff09;&#xff1a;Transform - 变换&#xff1a;坐标、朝向、大小Mesh Filter - 加载网格数据Mesh Renderer- 渲染网格Camera - …

Nessus 工具使用全攻略

目录 一、Nessus&#xff1a;网络安全的坚固防线 二、Nessus 安装指南 &#xff08;一&#xff09;获取安装包 &#xff08;二&#xff09;安装流程 三、初次配置&#xff1a;开启 Nessus 的第一步 &#xff08;一&#xff09;账号注册 &#xff08;二&#xff09;激活 …

PHP代驾系统小程序

&#x1f697; 代驾系统 —— 安全、便捷、智能的出行新选择 &#x1f527; 一款基于先进ThinkPHPUniapp技术架构&#xff0c;匠心独运的代驾软件横空出世&#xff0c;微信小程序端率先登场&#xff0c;为您的出行之旅增添前所未有的便捷与安全。它不仅是您贴心的出行助手&…

网络安全扫描--基础篇

前言 1、了解互联网安全领域中日趋重要的扫描技术 2、了解在不同网络场景下扫描技术手段 3、熟悉linux下系统内核防护策略并能大件一个有效的系统防护体系 4、增强工作安全意识&#xff0c;并能有效的实践于工作场景中 目录 1、熟悉主机扫描工具&#xff08;fping&#xff0c;…

【MySQL数据库】Ubuntu下的mysql

目录 1&#xff0c;安装mysql数据库 2&#xff0c;mysql默认安装路径 3&#xff0c;my.cnf配置文件? 4&#xff0c;mysql运用的相关指令及说明 5&#xff0c;数据库、表的备份和恢复 mysql是一套给我们提供数据存取的&#xff0c;更加有利于管理数据的服务的网络程序。下…

WebRTC与EasyRTC:开启智能硬件音视频通讯的全新旅程

在当今数字化时代&#xff0c;音视频通讯技术正以前所未有的速度革新着我们的生活与工作方式。WebRTC与EasyRTC作为这一领域的佼佼者&#xff0c;正携手为智能硬件的音视频通讯注入强大动力&#xff0c;开启全新的篇章。 一、WebRTC与智能硬件融合的崭新趋势 WebRTC技术&…

基于Ceedling的嵌入式软件单元测试

Ceedling 如果你使用 Ceedling&#xff08;一个针对 C 代码单元测试的构建管理器&#xff09;&#xff0c;可以更方便地管理测试。Ceedling 会自动处理 Unity 和 CMock 的集成&#xff0c;无需手动编写 Makefile。 1.环境搭建 1.1 Ruby环境 sudo apt-get install ruby1.2 安…

一文深入了解DeepSeek-R1:模型架构

本文深入探讨了 DeepSeek-R1 模型架构。让我们从输入到输出追踪 DeepSeek-R1 模型&#xff0c;以找到架构中的新发展和关键部分。DeepSeek-R1 基于 DeepSeek-V3-Base 模型架构。本文旨在涵盖其设计的所有重要方面。 &#x1f4dd; 1. 输入上下文长度 DeepSeek-R1的输入上下文长…

2024年终总结和2025年规划

2024年的主线是AI基础的学习和读书&#xff0c;虽然AI学习花费了更多的时间&#xff0c;但是读书长久看来于我是更重要的事情&#xff0c;哈哈哈&#xff0c;因此先简单回顾一下读书记忆&#xff0c;回顾我的2024&#xff0c;再展望一下我的2025. 我的2024年记忆 读万卷书&am…

5 .TCP传输 文件/数据

文件传输 本质:客户端通过标准IO或者文件IO&#xff0c;读取文件中的信息 然后将读取到的信息&#xff0c;通过套接字发送给服务器 服务器接收到后&#xff0c;立刻通过标准IO或者文件IO写到文件 这个过程&#xff0c;服务器要知道2件事 1&#xff1a;客户端发来的文件名字 …

哈希表(典型算法思想)—— OJ例题算法解析思路

目录 一、1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 算法代码&#xff1a; 1. 问题描述 2. 核心思路 3. 代码实现思路 &#xff08;1&#xff09;初始化哈希表 &#xff08;2&#xff09;遍历数组 &#xff08;3&#xff09;返回结果 4. 时间复杂度分析 …

Next.js【详解】CSS 样式方案

全局样式 Global CSS 默认已创建&#xff0c;即 src\app\globals.css&#xff0c;可根据需要修改 默认在全局布局中导入 src\app\layout.tsx import "./globals.css";组件样式 CSS Modules 新建文件 src\app\test\styles.module.css .red {color: red;}导入目标页面…

HCIA项目实践--RIP相关原理知识面试问题总结回答

9.4 RIP 9.4.1 补充概念 什么是邻居&#xff1f; 邻居指的是在网络拓扑结构中与某一节点&#xff08;如路由器&#xff09;直接相连的其他节点。它们之间可以直接进行通信和数据交互&#xff0c;能互相交换路由信息等&#xff0c;以实现网络中的数据转发和路径选择等功能。&am…

无人机信号调制技术原理

一、调制技术的必要性 频谱搬移&#xff1a;将低频的基带信号搬移到高频的载波上&#xff0c;便于天线辐射和传播。 信道复用&#xff1a; 利用不同的载波频率或调制方式&#xff0c;实现多路信号同时传输&#xff0c;提高信道利用率。 抗干扰&#xff1a; 通过选择合适的调…

leetcode-495.提莫攻击

leetcode-495.提莫攻击 文章目录 leetcode-495.提莫攻击一.题目描述二.代码提交三.解释 一.题目描述 二.代码提交 #include <vector> using namespace std;int findPoisonedDuration(vector<int>& timeSeries, int duration) {int total 0;for (int i 0; i …

Django 创建表时 “__str__ ”方法的使用

在 Django 模型中&#xff0c;__str__ 方法是一个 Python 特殊方法&#xff08;也称为“魔术方法”&#xff09;&#xff0c;用于定义对象的字符串表示形式。它的作用是控制当对象被转换为字符串时&#xff0c;应该返回什么样的内容。 示例&#xff1a; 我在初学ModelForm时尝…

img标签的title和alt

img标签的title和alt 显示上 title:鼠标移入到图片上时候显示的内容&#xff1b; alt:图片无法加载时候显示的内容; <div class"box"><div><!-- title --><h3>title</h3><img src"./image/poster.jpg" title"这是封…

Grafana——如何迁移Grafana到一台新服务器

背景 有时候由于服务器更新之类的&#xff0c;我们需要迁移一整套Grafana&#xff0c;这时候该怎么操作呢&#xff1f; 下面让我一步步说明下 安装Grafana 在新的服务器上安装Grafana 这个不再赘述&#xff0c;可以看一下我之前的文章 备份及迁移 迁移配置文件 配置文件即…

Flutter中 List列表中移除特定元素

在 Dart 语言里&#xff0c;若要从子列表中移除特定元素&#xff0c;可以使用以下几种方法&#xff0c;下面为你详细介绍&#xff1a; 方法一&#xff1a;使用 where 方法创建新列表 where 方法会根据指定的条件筛选元素&#xff0c;然后通过 toList 方法将筛选结果转换为新列…