简介

线段树是一种应用极其广泛,使用范围较广并且非常知名的树形数据结构,主要用于进行区间操作,如区间修改,区间查询等。这种数据结构唯一的不足就是巨大的代码量,因此处理一些较简单的问题时建议用树状数组。

原理

其实线段树的原理是比较简单的。我们平常见到的树都是没有特殊含义的,而线段树的每个点都表示一个区间。详细来讲,线段树其实是一颗二叉树,我们会把一个数组转换成一颗这样的树并进行多种操作。这是一颗线段树的大致样子。

可以看见,父节点表示的区间是两个子节点表示的区间拼出来的。但是如果不知道父节点或子节点是谁这颗树维护不了,我们因此可以定义一个结点u,并设它的左儿子为u*2,右儿子为u*2+1。这样子就不可能存在重复结点的情况。当然这样会消耗大量空间,因此要用到优化,我们待会儿讲。

可以看出来如果想表示区间中的一段只需把这些分散的区间组合起来。那我们应该怎么组合?我们假设查询的区间为[l,r],如果我们检测到一个区间被完全包含我们就返回它的值。否则,我们取该区间的中间,把这个区间割开。如果检测到查询区间有一部分在左边的区间,我们去查左边。查右边同理。这样,我们可以以logn的复杂度把序列多余的部分一点一点消掉,直到无法查询为止。

举个例子,我们现在要在上图中查询[1,3]的值。首先我们从结点1开始,这里没有完全包含,我们把结点1的区间从中间切开,变为[0,3]和[4,7],可以看出[4,7]与查询区间完全不包含,我们转而搜索[0,3]。

因为[0,3]依旧不是完全包含,我们将它切开,这是[2,3]被确定为完全包含,我们记录[2,3]的值,发现[0,1]里只有1包含,我们把它提出来,这样我们就把[1,3]拆成了1和[2,3]。

这时我们就完成了区间查询,但我们可能还需要进行区间修改。怎么做呢?我们只需让每个结点多维护一个懒标记。这个东西非常重要,因为如果每次我们都要把所有修改区间的子区间修改一遍复杂度不如直接修改。我们发现我们如果已经像之前把修改区间拆开,它们下面的结点目前没有必要改,我们完全可以等搜到子节点以后再改。这样修改也变成了logn的。

具体怎么操作?我们假设进行加操作,每次我们把修改的区间的懒标记加上修改值,等到有需要访问子节点的操作时,我们把子节点的和加上其区间长度乘懒标记的值,并把懒标记加给左右儿子,最后把自己的懒标记清空就可以了。

实现

建树

struct tree{int l,r,sum,lazy;
}t[4*N];
void build(int u, int l, int r){t[u].l=l,t[u].r=r;if(l==r){t[u].sum=a[l];return;}int mid=(l+r)>>1;build(u*2,l,mid), build(u*2+1,mid+1,r);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}

这里的u就是每个树上结点的编号,l和r表示这个结点表示的区间。当l=r说明这个结点表示一个元素,直接赋值即可。在搜索完子节点我们更新父节点的和。

懒标记下传

void pushdown(int u){if(t[u].lazy){t[u*2].sum+=t[u].lazy*(t[u*2].r-t[u*2].l+1);t[u*2+1].sum+=t[u].lazy*(t[u*2+1].r-t[u*2+1].l+1);t[u*2].lazy+=t[u].lazy, t[u*2+1].lazy+=t[u].lazy;t[u].lazy=0;		}
}

这里的lazy就是懒标记,这里我们把父节点的懒标记加给两个子节点,同时维护原来就该加的值。

区间修改

void update(int u, int l, int r, int c){if(t[u].l>=l&&t[u].r<=r){t[u].sum+=c*(t[u].r-t[u].l+1);t[u].lazy+=c;return;}pushdown(u);int mid=(t[u].l+t[u].r)>>1;if(mid>=l) update(u*2,l,r,c);if(r>mid) update(u*2+1,l,r,c);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}

这里的l,r,c都是区间加的参数,不会变,真正表示结点信息的是u。我们看上面的判断表示如果这个结点表示的区间被完全包含我们就给它带上懒标记,维护区间和。如果不完全包含我们就需要下传懒标记让每次查询时获得的都是正确的值。这里我们去中间值将区间拆开。r需要大于mid是因为第二个区间的左端点是mid+1,我们需要判断一下,这里写r>=mid+1也是可以的。

区间查询

int find(int u, int l, int r){if(t[u].l>=l&&t[u].r<=r) return t[u].sum;pushdown(u);int ans=0, mid=(t[u].l+t[u].r)>>1;if(mid>=l) ans+=find(u*2,l,r);if(r>mid) ans+=find(u*2+1,l,r);return ans;
}

这里的l,r同样是查询区间的左右端点,不会改变。可以看到我们每次查询儿子前都要下传懒标记,这样儿子的值才是对的。我们只需维护一个计数器一层一层把答案传上来即可。

优化

假设一个序列特别长,有一千万个元素。这个时候开4倍空间会爆炸。这时我们就要用到动态开点。我们怎么做?之前定义了u的左儿子是u*2,右儿子是u*2+1,但这样可能浪费大量空间。我们因此取消这个定义,维护一个计数器,表示目前有多少点。我们存两个变量表示一个结点的儿子,每次添加时我们让这个变量变成计数器,然后计数器++。

这样我们可以节省大量空间,但代码也极度复杂,这里就不放了。推荐一下OIwiki的模版。

例题及完整代码

区间加区间查模版

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,a[N],op,x,y,k;
struct tree{int l,r,sum,lazy;
}t[4*N];
void build(int u, int l, int r){t[u].l=l,t[u].r=r;if(l==r){t[u].sum=a[l];return;}int mid=(l+r)>>1;build(u*2,l,mid), build(u*2+1,mid+1,r);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}
void pushdown(int u){if(t[u].lazy){t[u*2].sum+=t[u].lazy*(t[u*2].r-t[u*2].l+1);t[u*2+1].sum+=t[u].lazy*(t[u*2+1].r-t[u*2+1].l+1);t[u*2].lazy+=t[u].lazy, t[u*2+1].lazy+=t[u].lazy;t[u].lazy=0;		}
}
void update(int u, int l, int r, int c){if(t[u].l>=l&&t[u].r<=r){t[u].sum+=c*(t[u].r-t[u].l+1);t[u].lazy+=c;return;}pushdown(u);int mid=(t[u].l+t[u].r)>>1;if(mid>=l) update(u*2,l,r,c);if(r>mid) update(u*2+1,l,r,c);t[u].sum=t[u*2].sum+t[u*2+1].sum;
}
int find(int u, int l, int r){if(t[u].l>=l&&t[u].r<=r) return t[u].sum;pushdown(u);int ans=0, mid=(t[u].l+t[u].r)>>1;if(mid>=l) ans+=find(u*2,l,r);if(r>mid) ans+=find(u*2+1,l,r);return ans;
}
signed main(){cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];build(1,1,n);while(m--){cin>>op;if(op==1){cin>>x>>y>>k;update(1,x,y,k);}else{cin>>x>>y;cout<<find(1,x,y)<<endl;}}return 0;
}

线段树进阶题目

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,q,mod,a[N],op,x,y,k;
struct tree{int l,r,sum,mul,add;
}t[4*N];
void build(int u, int l, int r){t[u].l=l,t[u].r=r,t[u].mul=1;if(l==r){t[u].sum=a[l]%mod;return;}int mid=(l+r)>>1;build(u*2,l,mid);build(u*2+1,mid+1,r);t[u].sum=t[u*2].sum+t[u*2+1].sum;t[u].sum%=mod;
}
void pushd(int u){if(t[u].mul!=1||t[u].add){int L=u*2,R=u*2+1;t[L].sum=(1ll*t[L].sum*t[u].mul+t[u].add*(t[L].r-t[L].l+1))%mod;t[R].sum=(1ll*t[R].sum*t[u].mul+t[u].add*(t[R].r-t[R].l+1))%mod;t[L].mul=(1ll*t[L].mul*t[u].mul)%mod;t[R].mul=(1ll*t[R].mul*t[u].mul)%mod;t[L].add=(1ll*t[L].add*t[u].mul+t[u].add)%mod;t[R].add=(1ll*t[R].add*t[u].mul+t[u].add)%mod;t[u].mul=1,t[u].add=0;}
}
void update1(int u, int l, int r, int k){if(t[u].l>=l&&t[u].r<=r){t[u].sum=(1ll*t[u].sum*k)%mod;t[u].mul=(1ll*t[u].mul*k)%mod;t[u].add=(1ll*t[u].add*k)%mod;return;}pushd(u);int mid=(t[u].l+t[u].r)>>1;if(l<=mid) update1(u*2,l,r,k);if(r>mid) update1(u*2+1,l,r,k);t[u].sum=(t[u*2].sum+t[u*2+1].sum)%mod;
}
void update2(int u, int l, int r, int k){if(t[u].l>=l&&t[u].r<=r){t[u].sum=(t[u].sum+1ll*k*(t[u].r-t[u].l+1))%mod;t[u].add=(t[u].add+k)%mod;return;}pushd(u);int mid=(t[u].l+t[u].r)>>1;if(l<=mid) update2(u*2,l,r,k);if(r>mid) update2(u*2+1,l,r,k);t[u].sum=(t[u*2].sum+t[u*2+1].sum)%mod;
}
int find(int u, int l, int r){if(t[u].l>=l&&t[u].r<=r) return t[u].sum;pushd(u);int mid=(t[u].l+t[u].r)>>1,ans=0;if(l<=mid) ans+=find(u*2,l,r);if(r>mid) ans+=find(u*2+1,l,r);return ans;
}
signed main(){cin>>n>>q>>mod;for(int i=1;i<=n;i++) cin>>a[i];build(1,1,n);while(q--){cin>>op;if(op==1){cin>>x>>y>>k;update1(1,x,y,k);}else if(op==2){cin>>x>>y>>k;update2(1,x,y,k);}else{cin>>x>>y;cout<<find(1,x,y)%mod<<endl;}}return 0;
}

这里再给大家推荐几个题单。

适合入门的题单

适合进阶的题单

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

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

相关文章

Maven 入门与进阶:聚合、继承与生命周期详解

Maven 是 Java 项目管理的核心工具&#xff0c;其强大的依赖管理、项目构建和模块化设计能力&#xff0c;极大地提升了开发效率。本文将深入探讨 Maven 的 聚合&#xff08;Multi-module&#xff09;、继承&#xff08;Inheritance&#xff09; 和 生命周期&#xff08;Lifecyc…

手搓MCP客户端动态调用多MCP服务,调用哪个你说了算!

01 引言 前两天&#xff0c;有个粉丝朋友咨询MCP服务如何动态调用&#xff0c;动态加载MCP服务的链接&#xff1f;我们都知道MCP客户端可以配置多个MCP服务的地址&#xff1a; spring.ai.mcp.client.sse.connections.server1.urlhttp://localhost:xxxx spring.ai.mcp.client.ss…

Go语言中的优雅并发控制:通道信号量模式详解

在Go语言的并发编程中&#xff0c;“通过通信共享内存”的设计哲学贯穿始终。当面对高并发场景时&#xff0c;无限制创建goroutine可能导致资源耗尽、CPU过载等问题&#xff0c;通道信号量模式&#xff08;Channel Semaphore Pattern&#xff09; 正是一种基于Go通道特性的优雅…

鸿蒙 NEXT开发中轻松实现人脸识别功能

大家好&#xff0c;我是 V 哥。 今天给大家介绍在 HarmonyOS 原生鸿蒙开发中&#xff0c;实现人脸识别功能&#xff0c;这个功能在常用的 APP 开发中上镜率还是很高的&#xff0c;在传统的 Android 或 iOS 开发中&#xff0c;通常我们要借助第三方库来实现&#xff0c;而在鸿蒙…

华为开发者空间训练营-优秀作品公布

排名标题总分奖品1手把手教你开发一个地区智能查询MCP&#xff0c;赋能地理位置类MCP服务的“零输入”无感交互95华为 freebuds 6i 蓝牙耳机2基于华为开发者空间云主机DeepSeek助力电商企业AI海报文案驱动的最佳实践落地 94华为 freebuds 6i 蓝牙耳机32小时基于华为开发者空间和…

基于Python与Tkinter开发的微博多功能自动化助手

文章目录 摘要 1. 背景与意义 2. 需求分析 3. 核心架构设计 3.1. 技术选型 3.2. 核心思想:UI与逻辑分离的异步架构 4. 深度模块化剖析 4.1. 微博核心API交互模块 4.2. 健壮性设计:代理与重试机制 4.3. GUI界面模块 (WeiboApp 类) 4.4. 异步任务处理模块 5. 难点分析与解决方案…

效果驱动复购!健永科技RFID牛场智能称重项目落地

近日&#xff0c;北京某养殖企业持续下单电子耳标识读器&#xff0c;在牛场智能称重中落地应用&#xff0c;通过自动、准确地识别牛只并记录体重数据&#xff0c;显著提升效率和数据精准度&#xff0c;实现了“效果驱动复购”的良性循环。健永科技RFID技术在北京某养殖企业智能…

计算机网络:2、TCP和UDP

2、TCP和UDP 简介 TCP(transmission Control Protocol)&#xff1a;是一种通信标准&#xff0c;它使应用程序和计算设备能够在网络上交换消息。它的设计目的是在互联网上发送数据包&#xff0c;并确保数据和信息在网络上的成功传递。UDP(the User Datagram Protocol)&#xf…

WEB安全篇:浏览器攻击原理及防护

1、XSS&#xff1a;跨站脚本攻击就是攻击者想尽一切办法将可以执行的代码注入到网页中。攻击者在web页面恶意插入HTML或script标签&#xff0c;当用户浏览该页面时&#xff0c;恶意代码就会被执行&#xff0c;从而达到攻击的目的。XSS利用的是用户对指定网站的信任。比如&#…

汇编语言学习2---GNU Debugger (GDB)

学习记录&#xff0c;在汇编语言 &#xff0c;我们面对的是机器码&#xff08;以汇编指令形式展现&#xff09;&#xff0c;所以断点要设置在机器码被加载到内存中的位置。 GEF插件使用 安装插件wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/pyecho source ~/.gdbinit-g…

谈谈架构的内容

一、架构的定义架构是一个界定不清的东西&#xff0c;我们很难讲清楚哪些东西是架构&#xff0c;哪些东西不是架构。但软件行业里其实人人都在搞架构&#xff0c;软件设计就是架构本身。架构这个词出现得很早&#xff0c;有些人认为是 NASA&#xff08;也可能是NATO&#xff09…

C#文件(夹)读取相关(完善中。。。)

前言阅读项目编辑器的代码时&#xff0c;发现好多与文件&#xff08;夹&#xff09;路径相关代码。本来自己之前对路径相关的东西就模模糊糊&#xff0c;希望通过这篇笔记能让自己模糊的地方明朗一下。" / " 与 " \ "你是否有过这样的疑惑&#xff1a;Wind…

FPGA DP1.4 With DSC解决方案

引言&#xff1a;迎接高清高刷时代的显示挑战随着8K分辨率、高刷新率、HDR和更广色域内容的普及&#xff0c;传统视频接口的带宽正面临极限。DisplayPort 1.4标准虽提供了高达32.4 Gbps的带宽&#xff08;HBR3速率&#xff09;&#xff0c;但要无压缩地传输8K60Hz 10bpp HDR视频…

新手向:Python开发简易网络服务器

Python网络服务器开发指南&#xff1a;从零开始的完整实现网络服务器基础概念网络服务器是互联网基础设施的核心组件&#xff0c;它本质上是一个持续运行的程序&#xff0c;负责监听特定端口&#xff08;如HTTP服务的80端口或HTTPS的443端口&#xff09;&#xff0c;处理来自客…

819 机器学习-决策树2

一、决策树的算法信息增益&#xff1a;某个属性带来的熵增1、决策树三大经典算法• ID3 → 信息增益 信息增益&#xff1a;某个属性带来的熵增• C4.5 → 信息增益率 信息增益率&#xff1a;信息增益自身熵• CART → 基尼指数&#xff08;分类&#xff09;&#xff1b;平方误…

Objective-C 版本的 LiveEventBus 效果

想要 Objective-C 版本的 LiveEventBus 效果&#xff08;跨页面/跨模块通信&#xff0c;支持粘性和非粘性事件&#xff09;。在 iOS 里对应的就是 NSNotificationCenter&#xff0c;但是它 默认不支持粘性事件&#xff0c;所以如果你想要“粘性”&#xff0c;需要自己封装一层。…

WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析七

上一篇&#xff1a;WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析六 如果有错误欢迎指正批评&#xff0c;在此只作为科普和参考。 C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\um\iphlpapi.h 文章目录CreateIpNetEntry&#xff1…

STM32F407VGT6从零建立一个标准库工程模板+VSCode或Keil5

一、前言 下载平台:STM32F407ZGT6 代码使用平台:VSCode 编译器:arm-none-aebi-gcc ---- 默认你已经安装 程序下载工具:STlink ---- 默认你拥有 批处理工具:make ---- 默认你已经安装 使用此方法可以不借助其它插件&#xff0c;例如:STM32EIDE。这个方法已经经过验证可以在STM3…

佩京VR党建工作站-党建VR系统-VR党建展厅

VR党建工作站是一种依托VR虚拟现实技术的数字化党建文化学习工具。它通过将丰富的学习内容植入到智慧党建科技产品中&#xff0c;构建出沉浸式的学习场景&#xff0c;从而创新了体验式学习模式&#xff0c;促进了党员的自主学习。VR党建工作站核心功能&#xff1a;1、了解实时新…

Kotlin 协程之Channel的概念和基本使用

前言 在 专栏 之前的文章中&#xff0c;我们已经知道了协程的启动、挂起、取消、异常以及常用的协程作用域等基础应用。 这些基础应用适合的场景是一次性任务&#xff0c;执行完就结束了的场景。 launch / async 适合的场景 网络请求数据库查询文件读写并行计算任务等等 而…