一、一维前缀和

1.1 定义

给定一个数组 a[1..n],其前缀和数组 pre[1..n] 定义为:

pre[i]=a[1]+a[2]+⋯+a[i] pre[i] = a[1] + a[2] + \dots + a[i] pre[i]=a[1]+a[2]++a[i]

pre[i] 表示原数组从第 1 项到第 i 项的和。

1.2 构建

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

1.3 区间求和

使用前缀和可以在 O(1)O(1)O(1) 时间内求任意区间 [l,r][l, r][l,r] 的和:

sum=pre[r]−pre[l−1] sum = pre[r] - pre[l - 1] sum=pre[r]pre[l1]

公式推导:

pre[r]=a[1]+a[2]+⋯+a[l−1]+a[l]+⋯+a[r]pre[l−1]=a[1]+a[2]+⋯+a[l−1] pre[r] = a[1] + a[2] + \dots + a[l-1] + a[l] + \dots + a[r] \\ pre[l-1] = a[1] + a[2] + \dots + a[l-1] pre[r]=a[1]+a[2]++a[l1]+a[l]++a[r]pre[l1]=a[1]+a[2]++a[l1]

所以两者一减,刚好剩下区间 [l,r][l, r][l,r] 的和。

1.4 应用场景

  • 快速计算区间和
  • 优化暴力 O(n2)O(n^2)O(n2) 的区间统计问题为 O(n)O(n)O(n)

1.5 示例

// 输入一个数组,求多个区间 [l, r] 的和
int a[N], pre[N];
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i];while (q--) 
{int l, r;cin >> l >> r;cout << pre[r] - pre[l - 1] << '\n';
}

二、一维差分数组

2.1 定义

对于原数组 a[1..n],其差分数组 diff[1..n] 定义为:

diff[i]=a[i]−a[i−1]  (i≥2),  diff[1]=a[1] diff[i] = a[i] - a[i - 1]\ \ (i \geq 2),\ \ diff[1] = a[1] diff[i]=a[i]a[i1]  (i2),  diff[1]=a[1]

2.2 构建

int a[N], diff[N];
diff[1] = a[1];
for (int i = 2; i <= n; i++) 
{diff[i] = a[i] - a[i - 1];
}

2.3 区间加法操作

若想对区间 [l,r][l, r][l,r] 所有数加上 ddd,只需:

diff[l] += d;
diff[r + 1] -= d;

原理在于:差分数组记录的是“增量”,只需要在区间起点加一个数、在区间终点的下一位减去这个数,就能确保中间所有位置都累加这个值。

然后通过前缀和还原整个数组:

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

2.4 区间减法操作

若想对区间 [l,r][l, r][l,r] 所有数减去 ddd,也可以使用差分数组:

diff[l] -= d;
diff[r + 1] += d;

原理类似:差分数组记录的是“增量”,如果我们想对一个区间 [l,r][l, r][l,r] 的所有元素减去 ddd,只需要在区间起点加上 −d-dd,在区间终点的下一位加上 +d+d+d,就能确保整个区间内的值都减少 ddd,而其他位置不受影响。这与区间加法操作完全对称,只是将 ddd 替换为 −d-dd


三、差分原理

3.1 目的

差分数组的核心目的是:将原本需要 O(n) 的区间修改操作优化为 O(1)

比如:我们想对数组中一段区间 [l,r][l, r][l,r] 的所有元素都加上一个数 ddd,若用原数组暴力加法,每次操作就要 O(r−l+1)O(r-l+1)O(rl+1);当有 mmm 次操作时,总复杂度是 O(mn)O(mn)O(mn),会超时。

3.2 差分数组的构建

我们构建一个数组 diff[1…n]diff[1 \ldots n]diff[1n]

diff[i]=a[i]−a[i−1] diff[i] = a[i] - a[i - 1] diff[i]=a[i]a[i1]

也可以理解为:

差分数组记录的是原数组中相邻两项之间的“变化量”。

根据这个定义,有:

a[i]=diff[1]+diff[2]+⋯+diff[i]⇒a[i]=∑j=1idiff[j] a[i] = diff[1] + diff[2] + \dots + diff[i] \Rightarrow a[i] = \sum_{j=1}^{i} diff[j] a[i]=diff[1]+diff[2]++diff[i]a[i]=j=1idiff[j]

也就是说,原数组可以通过差分数组做前缀和来还原

3.3 差分操作的核心逻辑

想要将区间 [l,r][l, r][l,r] 所有数都加上 ddd,我们可以在 diff[] 上做如下处理:

diff[l] += d;
diff[r + 1] -= d;
为什么这样就能完成整个区间的加法?

我们回忆差分的“还原”过程:

a[1] = diff[1];
a[2] = a[1] + diff[2];
a[3] = a[2] + diff[3];
...

你会发现,前缀相加会让 diff[l] 的影响一直传导下去。直到 diff[r+1] 把这部分影响抵消为止。

也就是说:

  • a[l]a[l]a[l]a[r]a[r]a[r] 都被加上了 ddd
  • a[r+1]a[r+1]a[r+1] 之后的数不受影响

这就实现了:O(1) 修改一个区间


四、一个形象的类比

假设你站在楼梯上,从第 1 级楼梯往上走,a[i] 是你在第 i 级台阶上的高度。

  • 前缀和:相当于你每走一步都记一下总共走了多远。
  • 差分数组:相当于你记录每一步你上升(或下降)了多少。

你只要知道每一步的变化量(差分数组),就能还原出每一级台阶的实际高度(原数组);你也能知道在一段范围内你升高了多少(前缀和)。



五、前缀和与差分的区别与联系

类别作用技术本质时间复杂度
前缀和快速查询区间和把每个位置存成前缀累加和查询 O(1)O(1)O(1),构建 O(n)O(n)O(n)
差分数组快速区间修改存储相邻值之间的“变化”修改 O(1)O(1)O(1),恢复 O(n)O(n)O(n)

它们的关系就像:

  • 前缀和是累计的“总和”
  • 差分是相邻的“变化量”

差分数组其实就是前缀和的逆操作。


六、常见问题汇总

  1. 差分数组的还原为什么用前缀和?

    差分数组记录的是相邻元素之间的增量,所以前缀和就是原数组。

  2. 差分数组是否支持区间乘法?

    不支持,差分只能处理区间加减。

  3. 差分数组是否可以从 0 开始?

    可以,但要注意下标对应的意义,最好从 1 开始更清晰。

  4. 是否每次都需要重建差分数组?

    看情况。如果每次操作互不干扰,可以复用;否则建议重建或静态开数组并清空。


七、总结

  • 前缀和用于高效区间查询
  • 差分数组用于高效区间修改
  • 两者配合使用是处理“区间加减 + 查询”类问题的利器

在处理数据量较大的题目(如 10510^510510610^6106)时,前缀和与差分是比线段树更快、更简洁的选择。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 双数据源配置

文章目录什么是双数据源&#xff1f;为什么需要双数据源&#xff1f;核心实现原理完整示例注意什么是双数据源&#xff1f; 双数据源是指在一个应用程序中同时配置和使用两个不同的数据库连接。比如&#xff1a; 一个连接订单数据库&#xff0c;处理业务数据一个连接用户中心…

【Java】【力扣】102.二叉树层序遍历

思路一个辅助队列&#xff08;初始化队列&#xff1a;根节点入队&#xff09;一个节点 出队&#xff0c;他的左右孩子入队循环 直到队列为空举例代码public List<List<Integer>> levelOrder(TreeNode root) {if (rootnull){return new ArrayList<List<Intege…

为什么有些PDF无法复制文字?原理分析与解决方案

在日常办公和学习中&#xff0c;我们经常会从PDF文件中复制文字&#xff0c;用于编辑、引用、整理笔记。但你是否也遇到过这样的情况&#xff1a;有些PDF中的文字根本无法选中&#xff0c;更无法复制粘贴&#xff1f; 看起来像是“文字”&#xff0c;但操作上却完全无效——这…

LabVIEW浏览器ActiveX事件交互

​程序围绕 WebBrowser ActiveX 控件&#xff0c;借 “Reg Event Callback” 注册标题变更回调&#xff0c;“Callback - Title Change.vi” 处理标题数据&#xff0c;“Monitor...” 响应 URL 变更&#xff0c;“Unregister...” 清理资源&#xff0c;实现浏览器事件交互与管控…

C++后端面试八股文

一、C 语言基础与底层原理请解释 new / delete 和 malloc / free 的区别和联系&#xff0c;以及使用它们时需要注意什么new 和 delete 是C的​​运算符&#xff08;Operator&#xff09;​​。这意味着它们可以被类&#xff08;通过 operator new 和 operator delete&#xff0…

基础分类模型及回归简介(一)

一、先搞懂两个核心任务&#xff1a;分类和回归咱们生活中总遇到要 “判断” 或 “预测” 的事&#xff1a;比如看到一个水果&#xff0c;判断是苹果还是橘子 —— 这就是分类&#xff08;结果是 “类别”&#xff09;&#xff1b;比如根据西瓜的大小、颜色&#xff0c;猜它能卖…

【LeetCode 热题 100】114. 二叉树展开为链表——(解法二)分治

Problem: 114. 二叉树展开为链表 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序…

【WPF】WPF 自定义控件 实战详解,含命令实现

&#x1f9e9;《WPF 自定义控件》实战详解本文将围绕如何编写一个自定义控件&#xff08;如带右键菜单的图片控件 ImageView&#xff09;&#xff0c;逐步讲解其定义、命令绑定与 ContextMenu 中常见的语法技巧。&#x1f9f1; 一、创建一个 WPF 自定义控件的步骤 WPF 中自定义…

Flink 2.0 DataStream算子全景

在实时流处理中&#xff0c;Apache Flink的DataStream API算子是构建流处理 pipeline 的基础单元。本文基于Flink 2.0&#xff0c;聚焦算子的核心概念、分类及高级特性。 一、算子核心概念&#xff1a;流处理的"原子操作 1. 数据流拓扑&#xff08;Stream Topology&#x…

Flask 入门到实战(2):使用 SQLAlchemy 打造可持久化的数据层

Flask 入门到实战&#xff1a;使用 SQLAlchemy 打造可持久化的数据层一、前言&#xff1a;为什么用 Flask-SQLAlchemy&#xff1f; 在 Python Web 开发中&#xff0c;操作数据库的方式主要有两种&#xff1a; 直接写 SQL&#xff08;繁琐且难维护&#xff09;使用 ORM&#xff…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | GithubProfies(GitHub 个人资料)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— GithubProfies组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API&#xff08;<script setup…

simscape中坐标系和坐标变换Frames and Transforms

为了更便捷地描述单个物体的运动&#xff0c;最好以该物体的质心为坐标原点建立坐标系&#xff0c;从而可以非常方便地描述其旋转运动。因此&#xff0c;在计算多个物体之间的位置关系时&#xff0c;为了计算方便&#xff0c;需要频繁地更换坐标框架&#xff0c;这也是multibod…

构建分布式光伏“四可”能力:支撑新型电力系统安全稳定运行的关键路径

随着我国新能源装机规模的跨越式增长&#xff0c;国家能源战略对新能源电站的规范化接入与精细化调度管理提出了更高要求。在电力市场化改革深化与新型电力系统构建的关键时期&#xff0c;保障电网安全稳定、提升新能源高效消纳能力已成为核心议题。国家能源局于2025年1月17日正…

UART寄存器介绍

在 STM32 微控制器中&#xff0c;UART&#xff08;通用异步收发传输器&#xff09;通信通过多个寄存器实现配置和数据传输。下面详细解析 UART 的核心寄存器及其功能。1. 状态寄存器&#xff08;USART_SR&#xff09;状态寄存器反映 UART 当前的工作状态&#xff0c;用于判断数…

写一个算法对一组值进行归一化映射,使它们在视觉上有明显的区分度,尤其在数据集分布不均时仍能体现差异

问题&#xff1a; 有一批数据&#xff0c;都是随机值范围是不确定&#xff0c;我需要用这个值来绘制同样数量圆&#xff0c;不同值他们的圆半径不同&#xff0c;考虑到数据有时候大小偏差不大&#xff0c;这1000个值有可能是集中在10,20之间&#xff0c;也可能是分布广泛&#…

具身智能零碎知识点(五):VAE中对使用KL散度的理解

VAE中对使用KL散度的理解什么是 VAE (Variational AutoEncoder)&#xff1f;从自编码器 (AE) 说起VAE&#xff1a;让潜在空间变得“有意义”和“连续”KL 散度是如何用到的&#xff1f;通俗理解 KL 散度在 VAE 中的作用&#xff1a;带来的好处&#xff1a;KL 散度公式 (无需背诵…

理解:进程、线程、协程

线程、进程和协程是并发编程的重要组成部分。进程&#xff08;Process&#xff09;定义进程是操作系统分配资源的基本单位&#xff0c;表示一个正在执行的程序。一旦一个程序被加载到内存中&#xff0c;它就成为一个进程&#xff0c;而每个进程都有其独立的内存空间。特征进程之…

总结一下找素数的三种方法

目录 一试除法 二埃氏筛 三线性筛(欧拉筛) 一试除法 思想&#xff1a;就是判断某个数x是不是素数,就判断从2开始到小于根号x的范围内有没有能够取余不等于0的,这个说明当前值就是x的一个因子&#xff0c;所以不是素数。 代码&#xff1a; import java.util.Scanner;public…

基于Yolov8车辆检测及图像处理系统【有代码】

0 引言 随着城市化进程的加速和机动车保有量的快速增长,交通管理、智能监控和自动驾驶等领域对车辆目标检测技术的需求日益增长。车辆目标检测是计算机视觉领域的一个重要研究方向,其目标是从图像或视频序列中准确识别和定位车辆,为后续的车辆跟踪、行为分析和交通流量统计…

MySQL密码管理器“mysql_config_editor“

目录 核心能力 常用命令速查 为什么更安全&#xff1f; 典型场景 mysql_config_editor 是 MySQL 官方自带的一款命令行小工具&#xff0c;作用一句话&#xff1a;把账号、密码、主机、端口等连接信息加密存起来&#xff0c;下次连接时只敲一个名字即可&#xff0c;不用再写…