线段树详解 原理解释 + 构建步骤 + 代码(带模板)
目录
介绍:
定义:
以具体一个题目为例:
树的表示方法:
实现步骤:
构建结点属性:
pushup函数:
build函数:
pushdown函数:
modify函数:
query函数:
如何记忆:
模板:
介绍:
线段树(Segment Tree)是一种常用的数据结构,用于解决涉及区间查询的问题。它主要用于在数组或列表等数据结构上支持以下两类查询操作:
- 区间查询:查询某个区间内的统计信息,例如求和、最大值、最小值等。
- 区间更新:修改数组中某个区间元素的值,并相应地更新线段树中的信息。
核心思想是将原始数据递归地划分成一系列不相交的区间,并在每个区间上维护一些预先计算好的信息,以支持高效的区间查询。
定义:
假设我们有一个包含 N 个元素的数组 A,线段树 T 是基于数组 A 的线段树。线段树 T 是一个满二叉树,它具有以下性质:
- 根节点表示整个数组的区间 [1, N]。
- 如果一个节点表示的区间是 [left, right],则它的左子节点表示的区间是 [left, mid],右子节点表示的区间是 [mid+1, right],其中 mid 是 left 和 right 的中间值。
- 叶子节点表示数组 A 中的单个元素,而内部节点表示对应区间上的预计算信息(如区间和、区间最大值等)。
- 线段树通常使用数组来模拟实现。
线段树算法一般包含以下五个函数:
1.build(); 初始构建一个线段树。
2.pushpu(); 向上传递信息。
3.pushdown(); 向下传递懒标记,并且更新子树。
4.modify(); 修改某一区间。
5.query(); 查询某一区间信息。
下面我们一个一个来介绍。
以具体一个题目为例:
下面解析以此题目为例子。
树的表示方法:
我们用 tr 数组来模拟这颗树。
假设根节点在 tr 数组中的的下标为为 i,那么其左右子树的下标为:
左:i * 2 (i << 1)
右:i * 2 + 1 (i << 1 | 1)
我们一般使用位运算,也就是括号里的,含义是一样的。所以可以计算出,tr 数组的长度最多就是题目所给数组长度的4倍。
实现步骤:
事先把输入的数组存在 w数组 中。
构建结点属性:
树结点其实就是一个区间,所以属性包含:左右边界,懒标记。
此题的懒标记就是区间需要加上的值 d 。
根据题目我们还需要查询区间的元素和,所以在其中添加一个 sum。
struct Node
{int l, r;LL sum;LL add; // 懒标记
}tr[N * 4];;
pushup函数:
我们在 build 一颗树之前,要先写 pushup 函数,用于向上传递信息,因为我们只知道叶子结点的值,我们要用后序遍历去构建父亲,所以要用到 pushup ,根据题目,我们要向上传递的信息显然是左右子树的 sum 和,这样就可以算出父亲的 sum 。
void pushup(int u) // 向上传递信息
{tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
build函数:
接下来我们开始构建这颗树,若区间内只有一个元素(l == r),说明我们找到了叶子结点,给叶子结点赋值,若不是结子节点(l != r),就继续向左右子树递归,在递归完成时(后序遍历)使用pushup,通过已经获得值的子树去更新父亲。
void build(int u, int l, int r)
{if (l == r) tr[u] = {l, r, w[l], 0}; // 叶子节点else{tr[u] = {l, r};int mid = l + r >> 1;build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 若不是叶子节点,向下递归pushup(u); // 通过子树构建父亲}
}
pushdown函数:
pushdown函数是给子树传递懒标记的,如果懒标记不为空,就将父亲的懒标记传递给左右子树,并且通过懒标记更新左右子树的信息,此题求的是sum,所以子树的 sum 值就要加上区间长度乘上父亲的懒标记,最后清空父亲的懒标记。
注意:懒标记表示的子树需要添加的信息,不包含父亲自己,所以在传递懒标记时,才要传递懒标记同时更新子树。
void pushdown(int u)
{auto &root = tr[u], &left = tr[u << 1], & right = tr[u << 1 | 1];if (root.add){// 传递懒标记并且更新子树left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;root.add = 0; // 删除懒标记}
}
modify函数:
修改区间信息,如果当前遍历的结点区间已经在区间中,那么就直接给其加上懒标记,并且计算更新其 sum。如果当前遍历的结点区间中的一部分是需要修改的区间,那么就先向下传递懒标记pushdown,然后在向需要修改的左右子树去递归,后序返回时,要给更新父亲pushup。
void modify(int u, int l, int r, int v)
{// 结点在要修改的区间中if (l <= tr[u].l && r >= tr[u].r){tr[u].sum += (tr[u].r - tr[u].l + 1) * v;tr[u].add += v; // 加上懒标记}else{pushdown(u); // 先传递懒标记int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) modify(u << 1, l, r, v);if (r > mid) modify(u << 1 | 1, l, r, v);pushup(u); // 更新父亲}
}
query函数:
用于查询区间信息,这里就是查询区间的sum。若遍历到的结点区间在查询区间之中,就返回其sum,若结点区间只有一部分在查询区间中,一样的,也是先传递懒标记,然后继续向需要计算的左右子树去递归,后序返回时计算结果。
LL query(int u, int l, int r)
{if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum; // 返回区间信息pushdown(u); // 也是先传递懒标记LL v = 0;int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) v = query(u << 1, l, r);if (r > mid) v += query(u << 1 | 1, l, r);return v;
}
如何记忆:
最重要的是注意每个函数pushup,pushdown函数的位置。只有在modify函数才两个一起用。
build函数只用一个pushup,query函数只用一个pushdown。
模板:
根据具体题目,自行修改。
// 操作是给区间每一个数加d
// 询问是求某一区间和
#include<iostream>using namespace std;typedef long long LL;
const int N = 100010;int w[N];
int n, m;struct Node
{int l, r;LL sum;LL add; // 懒标记
}tr[N * 4];;void pushup(int u) // 向上传递信息
{tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{auto &root = tr[u], &left = tr[u << 1], & right = tr[u << 1 | 1];if (root.add){// 传递懒标记并且更新子树left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;root.add = 0; // 删除懒标记}
}
void build(int u, int l, int r)
{if (l == r) tr[u] = {l, r, w[l], 0}; // 叶子节点else{tr[u] = {l, r};int mid = l + r >> 1;build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 若不是叶子节点,向下递归pushup(u); // 通过子树构建父亲}
}
void modify(int u, int l, int r, int v)
{// 结点在要修改的区间中if (l <= tr[u].l && r >= tr[u].r){tr[u].sum += (tr[u].r - tr[u].l + 1) * v;tr[u].add += v; // 加上懒标记}else{pushdown(u); // 先传递懒标记int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) modify(u << 1, l, r, v);if (r > mid) modify(u << 1 | 1, l, r, v);pushup(u); // 更新父亲}
}LL query(int u, int l, int r)
{if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum; // 返回区间信息pushdown(u); // 也是先传递懒标记LL v = 0;int mid = tr[u].l + tr[u].r >> 1;if (l <= mid) v = query(u << 1, l, r);if (r > mid) v += query(u << 1 | 1, l, r);return v;
}int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; ++i) scanf("%d", &w[i]); // 读入数组build(1, 1, n); // 以1为根节点,1~n区间建树char op[2];int l, r, t;// 读入修改和查询,q是查询,否则是修改while (m -- ){scanf("%s%d%d", op, &l, &r);if (*op == 'Q') printf("%lld\n", query(1, l, r));else{scanf("%d", &t);modify(1, l, r, t);}}return 0;
}
相关文章:

线段树详解 原理解释 + 构建步骤 + 代码(带模板)
目录 介绍: 定义: 以具体一个题目为例: 树的表示方法: 实现步骤: 构建结点属性: pushup函数: build函数: pushdown函数: modify函数: query…...
Java中Timer的使用
Timer 简述 在Java中,Timer(计时器)是一个用于安排定时任务的类。它可以实现在指定的时间间隔或指定的时间点执行某项任务或操作。 简单的来说Timer就是在Java中用来实现定时任务的工具。 Timer的API Timer中有两API可以使用分别是schedule…...
关于EJB,这两文把热闹和门道都说清楚了
关于技术的很多概念,如果你是小白,不建议看官网。原因就在于官网描述太抽象,就像八股文,看完感觉好像说了很多,但回过头又感觉似乎啥都没说。太虚、不接地气,是最大毛病。其实这些官网的打太极式的表述&…...

MixFormerV2: Efficient Fully Transformer Tracking
摘要 基于变压器的跟踪器在标准基准测试上取得了很强的精度。然而,它们的效率仍然是在GPU和CPU平台上实际部署的一个障碍。在本文中,为了克服这一问题,我们提出了一个完全变压器跟踪框架,称为MixFormerV2,没有任何密集…...

K8S中网络如何通信
Kubernetes 提出了一个自己的网络模型“IP-per-pod”,能够很好地适应集群系统的网络需求,它有下面的这 4 点基本假设: 集群里的每个 Pod 都会有唯一的一个 IP 地址。Pod 里的所有容器共享这个 IP 地址。集群里的所有 Pod 都属于同一个网段。…...
LangChain Agents深入剖析及源码解密上(三)
AutoGPT案例V1版本 AutoGPT是一个实验性的开源应用程序,展示了GPT-4语言模型的功能,AutoGPT程序由GPT-4驱动,将大语言模型的思考链接在一起,以自主实现设定的任何目标。作为GPT-4完全自主运行的首批例子之一,AutoGPT突破了人工智能的可能性。LangChain框架复现了https://g…...

分布式限流方案及实现
优质博文:IT-BLOG-CN 一、限流的作用和意义 限流是对高并发访问进行限制,限速的过程。通过限流来限制资源,可以提高系统的稳定性和可靠性,控制系统的负载,削峰填谷,保证服务质量。 服务限流后的常见处理…...

vuejs源码阅读之优化器
前面讲过vuejs中解析器是把html模版解析成AST,而优化器的作用是在AST中找到静态子树并打上标记。 静态子树是指的那些在AST中永远不会发生变化的节点。 例如,一个纯文本节点就是静态子树,而带变量的文本节点就不是静态子树,因为…...

【C++】-动态内存管理
作者:小树苗渴望变成参天大树 作者宣言:认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧! 文章目录 前言一、C内存管理方式1.1 new/delete操作内置类型 总结 前言 今天再讲一个…...
微服务SpringCloud教程——微服务是什么
微服务(MicroServices)最初是由 Martin Fowler 于 2014 年发表的论文《MicroServices》中提出的名词,它一经提出就成为了技术圈的热门话题。 微服务,我们可以从字面上去理解,即“微小的服务”,下面我们从“…...

RNN架构解析——LSTM模型
目录 LSTMLSTM内部结构图 Bi-LSTM实现 优点和缺点 LSTM LSTM内部结构图 Bi-LSTM 实现 优点和缺点...

苹果电脑系统优化工具:Ventura Cache Cleaner for mac
Ventura Cache Cleaner for Mac是一款专门为苹果电脑开发的系统优化工具,旨在帮助用户清理和优化Mac电脑,提高系统性能和速度。该软件由美国公司Northern Softworks开发,已经推出了多个版本,适用于不同版本的Mac操作系统。 Ventu…...
为了爱人穿越沙漠-心理测试
我觉得很准的一个心理测试。我的答案反射出我的态度,它们是100%的贴切。有兴趣的朋友也不妨一试。 你有一个深爱着的心上人,然而你们却被一片无垠的沙漠相隔两地,你禁不住思念的折磨,决定穿越沙漠去寻找你心中的那个爱人…… 1、…...

SpringBoot月度员工绩效考核管理系统【附任务书|ppt|万字文档(LW)和搭建文档】
主要功能 员工登录: ①首页、个人中心:修改密码、个人信息管理等 ②公告信息管理、绩效指标管理、绩效考核管理 管理员登录: ①首页、个人中心:修改密码、个人信息管理等 ②公告信息管理、部门管理、岗位管理、员工管理、绩效指标…...

【新星计划】STM32F103C8T6 - C语言 - 蓝牙JDY-31-SPP串口通信实验
文章目录 蓝牙技术的发展历史SPP蓝牙串口BLE协议(超低功耗应用蓝牙协议) 常见通用蓝牙模块JDY-31-SPPHC05/06 Keil 工程开发模版main.c 源文件:接线方式:烧录工具:FlyMcu串口调试工具:XCOM蓝牙调试助手APP …...

算法39:Excel 表列序号
一、需求 给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如: A -> 1 B -> 2 C -> 3 … Z -> 26 AA -> 27 AB -> 28 … 示例 1: 输入: columnTitle “A” 输出: 1 示例 2&…...
Android:ImageView xml方式配置selector 图片切换
1、在res/drawable目录下创建一个新的XML文件,比如selector_image.xml <?xml version"1.0" encoding"utf-8"?> <selector xmlns:android"http://schemas.android.com/apk/res/android"> <!-- 背景选择器 state_pre…...

Spring Boot 缓存 Cache 入门
Spring Boot 缓存 Cache 入门 1.概述 在系统访问量越来越大之后,往往最先出现瓶颈的往往是数据库。而为了减少数据库的压力,我们可以选择让产品砍掉消耗数据库性能的需求。 当然也可以引入缓存,在引入缓存之后,我们的读操作的代码ÿ…...

如何关闭谷歌浏览器自动更新
适用范围: 写自动化脚本时,需要安装浏览器驱动,安装浏览器驱动时需要下载对应的浏览器驱动版本,如果浏览器版本一直在自动更新的话,自动化脚本会报错浏览器版本和浏览器驱动不匹配,所以建议关闭谷歌浏览器自动更新&am…...

mybatis日志工厂
前言: 如果一个数据库操作,出现异常,我们需要排错,日志就是最好的助手 官方给我们提供了logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 默认工厂: 在配置文件里添加…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...