线段树详解

线段树详解 (原理,实现与应用) 线段树详解 By 岩之痕 一:综述 线段树是一种可以快速进行区间修改和区间查询的数据结构。点修改,区间修改和区间查询的复杂度都是O(log2(n)) 并且,线段树可以维护很多种类的信息。说到线段树就不得不提一下树状数组,树状数组维护的信息必须满足区间减法,因为树状数组只是维护1到X的信息,通过相减来得到区间信息,代码简单而且速度快,占用空间又少于线段树,所以一般情况下可以用树状数组的地方就 阅读详情

引入

例题:(洛谷 P3372 【模板】线段树 1)

已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上 k k k。
2.求出某区间每一个数的和。

我们可以使用树状数组来解决这道题,然而这次我们要回归正解了!我们要使用线段树!

线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。——百度百科

实 际 应 用 时 一 般 还 要 开 4 N 的 数 组 以 免 越 界 ! 实际应用时一般还要开 4N 的数组以免越界! 实际应用时一般还要开4N的数组以免越界!

4 N 的 数 组 ! 4N 的数组! 4N的数组!

4 N 的 数 组 ! 4N 的数组! 4N的数组!

(血淋淋的教训)

线段树能够支持满足结合律的运算的区间操作,查改的时间复杂度均为 O ( l o g n ) O(logn) O(logn),与树状数组相比,它的功能更强大,但是具有常数大,码量大的缺点。对于此,你需要记住以下三点:

  1. 树状数组能解决的问题,线段树一定能解决;
    线段树能解决的问题,树状数组不一定能解决。
  2. 在能用树状数组解决的情况下,尽量用树状数组解决;
    在对常数要求高的情况下,尽量用树状数组解决。
  3. 线段树的代码较为复杂,写挂了的话建议重写。

好,那我们进入正题。

在解决上述区间查改的问题之前,我们先来看一下这个问题。

对于这道树状数组的模板题,我们可以使用较为简单的线段树来实现。

我们来看一下线段树的结构图:
请添加图片描述

我们将其填入数字:
请添加图片描述

其中每一个长条就是所谓的“线段”,可以发现,父节点(父线段)被平均分成两个子节点(子线段)。

线段树的原理是什么呢?我们来看上面的图,在实际操作中,上图的线段实际上是不存在的,那么存在的是什么呢?是线段内各数的和。

这样,我们可以发现,每个节点的值都等于其两个儿子的和,叶子节点的值为原数。

请添加图片描述
我们来模拟一下单点修改的过程:

我们要将第 3 3 3 个数加上 2 2 2,也就是将 4 4 4 加 2 2 2。

首先,修改叶子节点:

请添加图片描述
依次向上累加,注意,在实际操作中只是将父节点更新为子节点之和(push_up),而并不需要更新线段上的数值,这里是为了便于理解。

最后更新完是这样的:
请添加图片描述
那查询呢?

假设我们要求 [ 2 , 4 ] [2,4] [2,4] 的和,这时我们要做相反的操作,即从根开始。

首先,在根节点找到需操作的区间:

请添加图片描述
向下分割:

请添加图片描述
继续分割,直到分割到完整的线段,返回线段的值:

请添加图片描述
请添加图片描述

至此,我们已经解决了单点修改和区间求和。代码如下:

#include<iostream>
#include<cstdio>
#define MAXN 500010
using namespace std;
int vis[MAXN*4];
int n,m;
int op,x,y;
int a[MAXN];
void push_up(int p)//把子节点传递给父节点
{
	vis[p]=vis[p<<1]+vis[p<<1|1];
}
void build(int p,int l,int r)
{
	if(l==r)//叶子节点直接赋值
	{
		vis[p]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);//左子树
	build(p<<1|1,mid+1,r);//右子树
	push_up(p);//上传
}
void update(int p,int l,int r,int x,int k)//修改
{
	if(l==r)
	{
		vis[p]+=k;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
		update(p<<1,l,mid,x,k);//左
	if(x>=mid+1)
		update(p<<1|1,mid+1,r,x,k);//右
	push_up(p);
}
int query(int p,int l,int r,int L,int R)
{
	if(L<=l&&R>=r)
		return vis[p];
	int mid=(l+r)>>1;
	int res=0;
	if(L<=mid)
		res+=query(p<<1,l,mid,L,R);
	if(R>=mid+1)
		res+=query(p<<1|1,mid+1,r,L,R);
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&op,&x,&y);
		if(op==1)
			update(1,1,n,x,y);
		else
			printf("%d\n",query(1,1,n,x,y));
	}
	return 0;
}

现在我们考虑一下区间查改的问题,显然,进行多次单点修改是不可行的,所以我们考虑更优的做法。

既然多次的单点修改是不行的,我们就要考虑直接的区间修改,然而可以发现,这样做的时间复杂度仍然不容乐观,因为它仍然需要依次修改所有的线段的值。

这时,我们就要采用这样一种思路:在查询时进行修改。查询是从根节点依次向下查询,所以我们可以给修改的线段打上标记,在查询时进行向下的传递(push_down)。这个标记我们给它一个形象的名字——懒标记。

有一个人 x x x,他有两个儿子 a a a 和 b b b,到了过年的时候,亲戚们把要送给 a a a 和 b b b 的红包交给了 x x x。 x x x 家里急需用钱,于是 x x x 私吞了这两个红包。
“一个 100 100 100 元,又一个 100 100 100 元,我赚了 2 × 100 = 200 元 2\times100=200元 2×100=200元”
“爸,钱呢”
“我先给你写个欠条,欠你 100 100 100 元,欠你 100 100 100 元,写好了”
“那我们用钱的时候怎么办”
“用钱就拿着欠条跟我要,要了钱得把欠条给我”

所谓“懒标记”,就是“欠条”。

这样,我们可以在查询时进行值的传递,而无需耗费多余的时间。

代码

#include<iostream>
#include<cstdio>
#define MAXN 100010
struct tree
{
	int vis;
	int tag;
}
t[MAXN*4];
int n,m;
int op,x,y,k;
int a[MAXN];
void push_up(int p)
{
	t[p].vis=t[p<<1].vis+t[p<<1|1].vis;
}
void build(int p,int l,int r)
{
	t[p].tag=0;
	if(l==r)
	{
		t[p].vis=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	push_up(p);
}
void push_tag(int p,int l,int r,int k)//处理懒标记
{
	t[p].tag+=k;
	t[p].vis+=k*(r-l+1);
}
void push_down(int p,int l,int r)//将懒标记向下传递
{
	if(t[p].tag==0)
		return;
	int mid=(l+r)>>1;
	push_tag(p<<1,l,mid,t[p].tag);
	push_tag(p<<1|1,mid+1,r,t[p].tag);
	t[p].tag=0;
}
void update(int p,int l,int r,int L,int R,int k)
{
	if(L<=l&&R>=r)
	{
		push_tag(p,l,r,k);
		return;
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(L<=mid)
		update(p<<1,l,mid,L,R,k);
	if(R>=mid+1)
		update(p<<1|1,mid+1,r,L,R,k);
	push_up(p);
}
int query(int p,int l,int r,int L,int R)
{
	if(L<=l&&R>=r)
		return t[p].vis;
	push_down(p,l,r);
	int mid=(l+r)>>1;
	int res=0;
	if(L<=mid)
		res+=query(p<<1,l,mid,L,R);
	if(R>=mid+1)
		res+=query(p<<1|1,mid+1,r,L,R);
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d",&x,&y,&k);
			update(1,1,n,x,y,k);
		}
		else
		{
			scanf("%d%d",&x,&y);
			printf("%d\n",query(1,1,n,x,y));
		}
	}
	return 0;
}//要过线段树1的话得开long long
线段树--概念+模板+入门题 引子 子问题–1 给出n个数,m次问询,每次问询给出一个范围l、r,求区间l–r的总和 很容易想到用前缀和可以解决 O(n) 子问题–2 给出n个数,m次修改,每次给出一个范围l、r和一个值k,将区间l—r的数全部+k,m次修改后,再问询1次求区间l–r的总和 也可以想到这用差分就能解决 O(n) 最后将这两个问题结合起来,给出n个数,m次操作,操作有两种一个是将区间l–r的值+k,一个是询问区间l–r的总和。 这个问题与之前不同的地方是,之前想要求一个范围只需处理一次数据算前缀和,然后数据不变,可以一 阅读详情

相关推荐

线段树详解(包含加法线段树、乘法线段树及区间根号线段树,简单易懂)

线段树及简单应用。

qi_programmer的博客 2582

【学习笔记】详解线段树(浅显易懂,匠心之作,图文并茂)

超详解线段树(浅显易懂,几乎涵盖所有线段树类型讲解,匠心之作,图文并茂)

weq2011的博客 3万+

线段树合并与线段树分裂

线段树合并是指建立一棵新的线段树,这棵线段树的每个节点都是两棵原线段树对应节点合并后的结果。线段树分裂实质上是线段树合并的逆过程。线段树分裂只适用于有序的序列,无序的序列是没有意义的,常用在动态开点的权值线段树。线段树分裂与线段树合并密不可分,一般来说没有只分裂不合并的题。的救灾粮,问所有操作结束后每个节点类型最多的救灾粮是那种。一棵树,点有点权,问每个节点的子树内有多少节点点权比它大。从这棵树的根节点开始递归分裂,当节点不存在时,递归结束。显然,对于两颗满的线段树,合并操作的复杂度是。

conti123的博客 934

算法数据结构——线段树(Segment Tree)精讲及python实现

线段树(Segment Tree):一种基于分治思想的二叉树,用于在区间上进行信息统计。它的每一个节点都对应一个区间leftright通常是整数。每一个叶子节点表示了一个单位区间(长度为1),叶子节点对应区间上。每一个非叶子节点的左子节点表示的区间都为,右子节点表示的的区间都为。线段树是一棵平衡二叉树,树上的每个节点维护一个区间。根节点维护的是整个区间,每个节点维护的是父亲节点的区间二等分之后的其中一个子区间。当有n。

白话机器学习 5171

用ESP32-S3和USB摄像头DIY一个无线图传监控(附完整代码和接线图)

本文详细介绍了如何利用ESP32-S3开发板和USB摄像头打造一套高性价比的无线图传监控系统。从硬件选型、接线图到代码实现,提供了完整的DIY方案,特别适合家庭监控、宠物看护等场景。通过优化WiFi传输和双核任务分配,实现了稳定的视频流传输,整套方案成本控制在200元以内。

weixin_33701251的博客 519

Steger算法(Line_Gauss)-光条中心线提取(基于Hessian矩阵)

Steger算法(Line_Gauss)-光条中心线提取(基于Hessian矩阵)算法背景介绍Hessian 矩阵与泰勒多项式关于求t导数与中心点、亚像素点高斯函数作用文献 算法背景介绍 在线结构光视觉传感器中,由线激光器发射出的线结构光,在本质上为一个连续且具有一定厚度的空间光平面,而在目标表面上所形成的具有一定宽度的光条特征,即为该光平面与目标表面相交而成的交线。在该空间光平面的厚度方向上,光强近似服从高斯分布,因而在摄像机采集到的光条图像中,在沿着光条宽度的方向或光条的法线方向上,其灰度也会呈现出类似

jiangxing11的博客 1万+

数据结构 线段树--权值线段树 详解

一、权值线段树 简介 1.线段树 线段树是一种用于维护区间信息的高效数据结构,可以在 O(log⁡N)O(\log N)O(logN) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。 线段树维护的信息必须满足可加性,即能以可以接受的速度合并信息和修改信息,包括在使用Lazy标记时,标记也要满足可加性 具体有关线段树的的讲解请参考博客:线段树 详解与模板

HeartFireY的博客 1万+

22、常见加密哈希算法及安全机制解析

本文详细解析了多种常见加密哈希算法,包括SHA系列、RIPEMD、Tiger、HAVAL、Whirlpool等,涵盖其工作原理、安全性与性能对比。同时探讨了哈希攻击方式如预映像攻击和彩虹表,并介绍了盐、MAC、HMAC等防御机制。结合密码存储、消息完整性验证及区块链应用案例,分析了不同场景下的算法选择建议,并展望了哈希算法在创新、融合与标准化方面的未来发展趋势。

seed的博客 390

详解线段树

但是在做题的时候还是会对模板进行修改,修改的点主要在区间val的意义上,我们这里模板里面val代表的是区间和,但后面题目里面可能是 区间最大值 、区间最小值…,但是万变不离其宗,线段树的结构是不会变的。

潜心钻研技术 1353

TriCore架构-基于TC397的启动流程

TC397芯片的启动流程分为三个关键阶段:固件启动(BootFirmware)、Bootloader引导和应用程序启动。BootFirmware作为硬件信任根,包含SSW基础初始化、CHSW自检和BSL应急刷写通道。Bootloader承担安全验证和程序引导功能,支持双分区(AB-SWAP)和双引导方案(PBL/SBL)。安全启动通过校验机制确保软件完整性,提供三种可选技术方案。应用程序启动阶段实现软件双分区管理,支持SOTA更新。整个流程体现了汽车电子系统的安全设计理念,通过硬件级保护与软件验证相结合确保

xiandang8023的博客 987

《HiVT: Hierarchical Vector Transformer for Multi-Agent Motion Prediction》论文阅读之HiVT

目录摘要1.介绍2.相关工作3.方法3.1. 总体框架3.2 场景表示 3.3 分层向量Transformer3.3.1 局部编码器3.3.2 全局交互模块3.3.3 多模态未来解码器3.4 训练4.实验4.1 实验设置4.2 消融研究 4.3. 结果5.结论 准确预测周围交通参与者的未来运动对于自动驾驶车辆的安全至关重要。最近,矢量化方法由于能够捕捉交通场景中的复杂交互而在运动预测领域占据主导地位。然而,现有的方法忽略了问题的对称性并且受到昂贵的计算成本的影响,面临着在不牺牲预测性能的情况

Gavinmiaoc的博客 3340

二维线段树详解

如题

INF_512的博客 2098

C#上位机面试必问30题:机器视觉+工业通信+性能优化(含答案解析)

工业通信:围绕“稳定性”展开,重点答异常处理、断线重连、资源释放;机器视觉:围绕“精度+速度”展开,重点答预处理、ROI、参数优化;性能优化:围绕“UI不卡、内存不泄、效率不低”展开,重点答异步、限流、资源复用;面试加分项:结合实际项目案例(如“我在XX项目中用信号量解决了多设备并发卡顿问题”)。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1509

线段树 详解

线段树

wuyixiang___的博客 1941

200Gb/s以上速率的通道信号完整性设计和优化经验(二)

为满足200Gb/s信号完整性需求,需综合优化PCB、连接器、封装及SERDES架构。关键措施包括:采用低粗糙度铜箔与低损耗材料降低传输线损耗(200G目标0.55–0.6dB/cm);通过背钻工艺控制残桩长度至4±2mil;缩减连接器焊盘残桩至600μm以内以消除谐振;封装层面推进跳层走线、低Dk/Df材料与可控SMT工艺;采用PAM4调制与基于ADC/DSP的混合信号架构实现高效均衡。先进CMOS工艺(如纳米片器件)支撑复杂数字处理,协同设计是突破速率瓶颈的核心。

小孟同学的博客 147

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

详解动态开点线段树

本文介绍了动态开点线段树及其在解决CF915E问题中的应用。由于数据范围过大(n≤1e9),传统线段树无法直接使用。动态开点线段树的核心思想是只创建操作中实际需要的节点,而非预先构建完整结构。文章通过图解展示了动态开点的过程,并提供了关键代码实现,包括修改操作和pushdown处理。最后给出了完整代码,并指出动态开点技术在可持久化线段树中的重要性。这种方法有效解决了大区间范围下的线段树实现问题。

Benny_250的博客 937

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

线段树及线段树模板

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为Olog⁡NO(\log N)OlogN。而未优化的空间复杂度为2N2N2N,实际应用时一般还要开空间为4N4N4N的数组以免越界,因此有时需要离散化让空间压缩。线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

AllenAC的博客 1234

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

线段树入门详解

目录 线段树入门详解 本文适用人群 线段树大致思路 本人代码习惯 单点修改 单点查询 区间查询 区间加减 Last 线段树入门详解 本文适用人群 不算特别入门啦...

dieloupi0136的博客 321
上一篇: 单源最短路径算法
下一篇: 平衡树详解(Splay)
Wu_while
博客等级 码龄6年 50粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值