线段树模板初讲

news2025/1/19 8:27:56

线段树模板初讲

文章目录

  • 线段树模板初讲
    • 引入
    • 数据结构
    • 操作(以求和为例)
      • pushup
      • build
      • 单点操作,区间查询
        • modify
        • query
      • 区间操作,区间操作
        • pushdown
        • modify
        • query
    • 例题
      • AcWing 1275. 最大数
        • 思路
        • 代码
      • AcWing 243. 一个简单的整数问题2
        • 思路
        • 代码
    • 总结

引入

线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。
线段树可以在 O ( log ⁡ N ) O(\log N) O(logN) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
线段树所维护的信息,需要满足区间加法。
区间加法:如果一个区间 [l,r]
(线段树中一个点表示一个区间)满足区间加法的意思是一个区间 [l,r]的线段树维护的信息(即区间最大值,区间最小值,区间和,区间 gcd
等),可以由两个区间 [l,mid]和 [mid+1,r] 合并而来。

数据结构

在这里插入图片描述
线段树特点

  1. 每个节点表示一个区间 [ l , r ] [l, r] [l,r],并记录着区间中的一些性质。
  2. 对于一个节点x,其区间为 [ l , r ] [l, r] [l,r],且 l ≠ r l \neq r l=r m i d = ⌊ l + r 2 ⌋ mid = \lfloor\cfrac{l + r}{2}\rfloor mid=2l+r。其子节点为 x < < 1 x << 1 x<<1 x < < 1 ∣ 1 x << 1|1 x<<1∣1,它们的区间分别为 [ l , m i d ] [l, mid] [l,mid] [ m i d + 1 , r ] [mid + 1, r] [mid+1,r]
  3. 其存储方式是堆的形式,也就是节点存于数组,父节点和子节点是2倍关系。
  4. 线段树的存储数组大小一般是 4 n 4n 4n

线段树结构的Python实现

# 节点的定义
def Tree :
	def __init__(self) :
		# 用于描述节点表示区间
		self.l = 0
		self.r = 0
		# 节点表示区间的性质
		self.v = 0
		# 懒标记
		self.add = 0
tr = [Tree() for _ in range(N * 4)]

操作(以求和为例)

pushup

具体作用:根据子节点推导父节点的性质的值

def pushup(u) :
	tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v

build

具体作用:根据已给信息构建线段树,也可以说是初始化。

def build(u, l, r) :
	tr[u].l, tr[u].r = l, r # 初始化节点区间
	if l == r : # 叶子节点,递归出口
		tr[u].v = a[l]
		return
	# 非叶子节点,继续递归构造子孙节点
	mid = l + r >> 1
	build(u << 1, l, mid)
	build(u << 1 | 1, mid + 1, r)
	pushup(u) # 用子节点更新当前节点的值

单点操作,区间查询

modify

对于单点修改,大致流程是,沿着包含目标节点的路径,找到目标节点并修改,最后回溯,用子节点将往上的节点更新。

def modify(u, x, d) :
	# 找到目标节点并修改
	if tr[u].l == x and tr[u].r == x :
		tr[u].v = d
		return
	# 未找到目标节点,继续延伸
	mid = tr[u].l + tr[u].r >> 1
	if x <= mid : # 节点在左半段情况
		modify(u << 1, x, d)
	else :
		modify(u << 1 | 1, x, d)
	pushup(u) # 更新当前区间节点值

query

向下递归的找区间的值。
如果目前递归的节点区间被包含在目标区间内,可以直接返回相应区间性质的值。
如果相交,与目标区间左相交或者右相交则可在相应子区间中查找被包含的区间值。

def query(u, l, r) :
	# 目前递归的节点区间被包含在目标区间内
	if l <= tr[u].l and tr[u].r <= r :
		return tr[u].v
	mid = tr[u].l + tr[u].r >> 1
	res = 0
	# 相交情况考虑
	if l <= mid : res = query(u << 1, l, r)
	if r > mid : res += query(u << 1 | 1, l, r)
	return res

区间操作,区间操作

如果要求修改区间 [l,r],把所有包含在区间 [l,r] 中的节点都遍历一次、修改一次,时间复杂度无法承受。我们这里要引入一个叫做 「懒惰标记」 的东西。
懒惰标记,简单来说,就是通过延迟对节点信息的更改,从而减少可能不必要的操作次数。每次执行修改时,我们通过打标记的方法表明该节点对应的区间在某一次操作中被更改,但不更新该节点的子节点的信息。实质性的修改则在下一次访问带有标记的节点时才进行。

这里的懒惰标记一般只作用于当前节点的子节点区间性质计算上,并不会对本层节点有任何影响。

pushdown

作用:向下传懒标记,子节点性质根据懒标记修改,并将当前层懒标记去除。

def pushdown(u) :
	root, left, right = tr[u], tr[u << 1], tr[u << 1 | 1]
	# 向下传懒标记,子节点性质根据懒标记修改
	if root.add :
		left.add += root.add
		right.add += root.add
		left.v += (left.r - left.l + 1) * root.add
		right.v += (right.r - right.l + 1) * root.add
		root.add = 0 #将当前层懒标记去除

modify

额外操作:每次遇到目标区间包含的区间节点,进行修改性质和标记懒标记。
否则,需要进行递归处理子区间,在这之前要向下传递懒标记。

def modify(u, l, r, d) :
	# 遇到目标区间包含的区间节点
	if l <= tr[u].l and tr[u].r <= r :
		tr[u].add += d
		tr[u].v += (tr[u].r - tr[u].l + 1) * d
		return
	else :
		pushdown(u)
		mid = tr[u].l + tr[u].r >> 1
		if l <= mid : # 与左半段相交
			modify(u << 1, l, r, d)
		if r > mid : # 与右半段相交部分
			modify(u << 1 | 1, l, r, d)
		pushup(u)

query

额外操作:递归处理子区间,在这之前要向下传递懒标记。

def query(u, l, r) :
	# 目标区间包含当前节点区间
	if l <= tr[u].l and tr[u].r <= r :
		return tr[u].v
	else :
		# 下传懒标记
		pushdown(u)
		mid = tr[u].l + tr[u].r >> 1
		res = 0
		if l <= mid :
			res = query(m << 1, l, r)
		if l > mid :
			res += query(m << 1 | 1, l, r)
		return res

例题

AcWing 1275. 最大数

给定一个正整数数列 a1,a2,…,an,每一个数都在 0∼p−1之间。

可以对这列数进行两种操作:

添加操作:向序列后添加一个数,序列长度变成 n+1

询问操作:询问这个序列中最后 L
个数中最大的数是多少。
程序运行的最开始,整数序列为空。

一共要对整数序列进行 m
次操作。

写一个程序,读入操作的序列,并输出询问操作的答案。

输入格式
第一行有两个正整数 m,p
,意义如题目描述;

接下来 m
行,每一行表示一个操作。

如果该行的内容是 Q L,则表示这个操作是询问序列中最后 L
个数的最大数是多少;

如果是 A t,则表示向序列后面加一个数,加入的数是 (t+a) mod p
。其中,t
是输入的参数,a
是在这个添加操作之前最后一个询问操作的答案(如果之前没有询问操作,则 a=0
)。

第一个操作一定是添加操作。对于询问操作,L>0
且不超过当前序列的长度。

输出格式
对于每一个询问操作,输出一行。该行只有一个数,即序列中最后 L
个数的最大数。

数据范围
1≤m≤2×105
,
1≤p≤2×109
,
0≤t<p
输入样例:
10 100
A 97
Q 1
Q 1
A 17
Q 2
A 63
Q 1
Q 1
Q 3
A 99
输出样例:
97
97
97
60
60
97
样例解释
最后的序列是 97,14,60,96。

思路

一共两种操作

  1. 向序列最后添加一个数
  2. 询问序列最后L个数中最大的数
    单点操作、区间查询
    由于加入的最后一个数需要前一个最大的数来计算,所以每次都要记录前一个数。

代码

from sys import stdin
N = 200010

class Tree :
	def __init__(self) :
		self.l = 0
		self.r = 0
		self.v = 0 # 表示最大值
tr = [Tree() for _ in range(N * 4)]

def pushup(u) :
	tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v)

def build(u, l, r) :
	tr[u].l, tr[u].r = l, r
	if l == r :
		return
	mid = (l + r) >> 1
	build(u << 1, l, mid)
	build(u << 1 | 1, mid + 1, r)
	pushup(u)

def modify(u, x, c) :
	if tr[u].l == tr[u].r == x :
		tr[u].v = c
		return
	mid = tr[u].l + tr[u].r >> 1
	if x <= mid : modify(u << 1, x, c)
	else : modify(u << 1 | 1, x, c)
	pushup(u)

def query(u, l, r) :
	if l <= tr[u].l and tr[u].r <= r :
		return tr[u].v
	else :
		res = 0
		mid = tr[u].l + tr[u].r >> 1
		if mid >= l :
			res = query(u << 1, l, r)
		if r > mid :
			res = max(res, query(u << 1 | 1, l, r))
		return res

last, index = 0, 0
n, m = map(int, stdin.readline().split())

build(1, 1, n)
for i in range(n) :
	cmd = stdin.readline().split()
	op, c = cmd[0], int(cmd[1])
	if op == "Q" :
		last = query(1,index - c + 1 , index)
		print(last)
	else :
		modify(1, index + 1, (last + c) % last)
		index += 1

AcWing 243. 一个简单的整数问题2

给定一个长度为 N的数列 A,以及 M条指令,每条指令可能是以下两种之一:C l r d,表示把 A[l],A[l+1],…,A[r]都加上 d。Q l r,表示询问数列中第 l∼r个数的和。对于每个询问,输出一个整数表示答案。

输入格式
第一行两个整数 N,M。

第二行 N 个整数 A[i]。

接下来 M行表示 M条指令,每条指令的格式如题目描述所示。

输出格式
对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围
1≤N,M≤105
,
|d|≤10000
,
|A[i]|≤109
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
输出样例:
4
55
9
15

思路

典型的区间操作,区间查询的题,懒标记出厂。

代码

from sys import stdin
N = 100010

class Tree :
	def __init__(self) :
		self.l = 0
		self.r = 0
		self.v = 0 # 表示区间和
		self.add = 0 # 懒标记

tr = [Tree() for _ in range(N * 4)]
a = [0] * N

def pushup(u) :
	tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v

def pushdown(u) :
	root, left, right = tr[u], tr[u << 1], tr[u << 1 | 1]
	if root.add :
		left.add += root.add
		right.add += root.add
		left.v += (left.r - left.l + 1) * root.add
		right.v += (right.r - right.l + 1) * root.add
		root.add = 0

def build(u, l, r) :
	tr[u].l, tr[u].r = l, r
	if l == r :
		tr[u].v = a[l]
		return
	mid  = l + r >> 1
	build(u << 1, l, mid)
	build(u << 1 | 1, mid + 1, r)
	pushup(u)

def modify(u, l, r, d) :
	if l <= tr[u].l and tr[u].r <= r :
		tr[u].v += (tr[u].r - tr[u].l + 1) * d
		tr[u].add += d
		return
	pushdown(u)
	mid = tr[u].l + tr[u].r >> 1
	if l <= mid :
		modify(u << 1, l, r, d)
	if r > mid :
		modify(u << 1 | 1, l, r, d)
	pushup(u)

def query(u, l, r) :
	if l <= tr[u].l and tr[u].r <= r :
		return tr[u].v
	pushdown(u)
	mid  = tr[u].l + tr[u].r >> 1
	res = 0
	if l <= mid :
		res = query(u << 1, l, r)
	if r > mid :
		res += query(u << 1 | 1, l, r)
	return res

n, m = map(int, input().split())
a[1 : n + 1] = list(map(int, input().split()))

build(1, 1, n)

for _ in range(m) :
	cmd = stdin.readline().split()
	op, l, r = cmd[0], int(cmd[1]), int(cmd[2])
	if op == 'Q' :
		print(query(1, l, r))
	else :
		d = int(cmd[3])
		modify(1, l, r, d)

总结

线段树就是牛,上面是关于线段树的简单讲解,分别关于区间查询,区间操作,和单点操作。对于线段树更多性质的记录和结合,可以解决很多很神奇的同时,但也就会使关系复杂。这个我们下次再言。

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

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

相关文章

systemV共享内存

systemV共享内存 共享内存区是最快的IPC形式。共享内存的大小一般是4KB的整数倍&#xff0c;因为系统分配共享内存是以4KB为单位的&#xff08;Page&#xff09;&#xff01;4KB也是划分内存块的基本单位。 之前学的管道&#xff0c;是通过文件系统来实现让不同的进程看到同一…

通用SQL查询分析器

技术&#xff1a;Java、JSP等摘要&#xff1a;本文主要针对当前很多软件都无法实现跨数据库、跨平台来执行sql语句而用户又仅需做一些基本的增删改查操作的矛盾&#xff0c;设计了一个能够跨平台跨数据库的软件。此软件是一个通用SQL查询分析器&#xff0c;利用java语言本身的跨…

rust中如何利用generic与PhantomData来实现更清晰的接口

前两天看了一个在 rustlang 中如何利用 generic 和 PhantomData 来让我们的 api 更加合理的视频&#xff0c; 当时看完就想写一篇相关内容的文章&#xff0c; 但是没有立即动手&#xff0c;一推迟&#xff0c;不出意外的忘了。这两天又接手了一个半成品的项目&#xff0c; 需要…

C++程序调用IsBadReadPtr或IsBadWritePtr引发内存访问违例问题的排查

目录 1、问题描述 2、VS中看不到有效的信息&#xff0c;尝试使用Windbg去分析 3、使用Windbg分析 4、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/12427258…

数据结构-链表-单链表(3)

目录 1. 顺序表的缺陷 2. 单链表 2.1 单链表的基本结构与接口函数 2.2 重要接口 创建新节点的函数&#xff1a; 2.2.1 尾插 2.2.2 头插 2.2.3 尾删 2.2.4 头删 2.2.5 查找 2.2.6 插入 2.2.7 删除 2.2.8 从pos后面插入 2.2.9 从pos后面删除 3. 链表的缺陷与优势&…

传输数据格式:JSON 异步加载

JSON JSON是一种传输数据的格式&#xff08;以对象为样板&#xff0c;本质上就是对象&#xff0c;但用途有区别&#xff0c;对象就是本地用的&#xff0c;json是用来传输的&#xff09;JSON.parse();string --> jsonJSON.stringify();json --> string json ---> {n…

关于安卓的一些残缺笔记

安卓笔记Android应用项目的开发过程Android的调试Android项目文档结构Intent的显式/隐式调用Activity的生命周期1个Activity界面涉及到生命周期的情况2个Activity界面涉及到生命周期的情况Android布局的理论讲解Activity界面布局ContentProvider是如何实现数据共享Android整体架…

mysql视图和存储过程

视图视图就是将一条sql查询语句封装起来&#xff0c;之后使用sql时&#xff0c;只需要查询视图即可&#xff0c;查询视图时会将这条sql语句再次执行一遍。视图不保存数据&#xff0c;数据还是在表中。SELECT 语句所查询的表称为视图的基表&#xff0c;而查询的结果集称为虚拟表…

ATTCK v10版本战术实战研究—持久化(一)

一、前言“在网络安全的世界里&#xff0c;白帽子与黑帽子之间无时无刻都在进行着正与邪的对抗&#xff0c;似乎永无休止。正所谓&#xff0c;道高一尺魔高一丈&#xff0c;巨大的利益驱使着个人或组织利用技术进行不法行为&#xff0c;花样层出不穷&#xff0c;令人防不胜防。…

udk2017环境搭建编译步骤

win10 64bit系统 1.参考minnowboard-max-rel-1-01-bin-releasenotes-for-binary-firmware-images.TXT MyWorkspace.rar 解压到c:\&#xff0c;参考txt中的git操作 3.复制ASL,NASM 到c&#xff1a;\ 安装vs2015 &#xff0c;勾选sdk 5.安装 python-2.7.10.amd64.msi&#xf…

【论文泛读】NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis

NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis | NeRF: 用于视图合成的神经辐射场的场景表示 | 2020年 出自文献&#xff1a;Mildenhall B, Srinivasan P P, Tancik M, et al. Nerf: Representing scenes as neural radiance fields for view synth…

泼辣修图Polarr5.11.4 版,让你的创意无限延伸

泼辣修图是一款非常实用的图片处理软件&#xff0c;它不仅拥有丰富的图片处理功能&#xff0c;而且还能够轻松地实现自定义操作。泼辣修图的操作界面非常简洁&#xff0c;功能也非常丰富&#xff0c;使用起来非常方便快捷。 泼辣修图拥有非常丰富的图片处理功能&#xff0c;包括…

【冲刺蓝桥杯的最后30天】day1

大家好&#x1f603;&#xff0c;我是想要慢慢变得优秀的向阳&#x1f31e;同学&#x1f468;‍&#x1f4bb;&#xff0c;断更了整整一年&#xff0c;又开始恢复CSDN更新&#xff0c;从今天开始逐渐恢复更新状态&#xff0c;正在备战蓝桥杯的小伙伴可以支持一下哦&#xff01;…

Rockchip Android13 GKI开发指南

Rockchip Android13 GKI开发指南 文章目录Rockchip Android13 GKI开发指南GKI介绍Google upstream kernel下载及编译Rockchip SDK中GKI相关目录介绍Rockchip GKI编译代码修改编译固件烧写KO编译及修改添加新的模块驱动的方法调试ko方法开机log确认uboot阶段Android阶段KO加载KO…

Java IO流详解

文章目录一、File1.1 构造方法1.2 文件操作 方法1.3 目录操作 方法1.4 文件检测 方法1.5 获取文件信息 方法1.6 应用练习二、IO 流2.1 InputStream 字节输入流 (读)&#x1f353;FileInputStream&#x1f353;BufferedInputStream2.2 OutputStream 字节输出流 (写)&#x1f34c…

【Redis】redis大key和大value的危害,如何处理?

前序 还记得上次和同事一起去面试候选人时&#xff0c;同事提了一个问题&#xff1a;Redis的大key有什么危害&#xff1f;当时候选人主要作答的角度是一个key的value较大时的情况&#xff0c;比如&#xff1a; 内存不均&#xff1a;单value较大时&#xff0c;可能会导致节点之…

[经验分享]gpt-3.5-Turbo|unity中实现http接口调用gpt新接口以及信息处理的实现案例分享

最近openAI发布了目前chatGPT所使用的模型gpt-3.5-Turbo&#xff0c;之前使用了text-davinci-003模型做了一个galgame的AI女友对话的demo。这次趁着新接口的发布&#xff0c;对这个demo也同步更新了模型调用的代码。本篇文章将分享一下&#xff0c;如何在unity里使用UnityWebRe…

记录一次PWM信号异常问题

问题我使用单片机输出PWM控制机械臂&#xff0c;但是控制过程中&#xff0c;机械臂总是会出现莫名的抽动。利用示波器测试PWM信号&#xff0c;发现信号正常。过程&#xff08;1&#xff09;在反复的测试过程中&#xff0c;队友提出&#xff0c;将示波器的地线放在左侧的GND波形…

计算机EI会议论文,和EI期刊论文有什么区别? - 易智编译EaseEditing

EI期刊论文&#xff0c;是期刊论文的一种。顾名思义&#xff0c;就是指发在期刊上的论文。 期刊论文发表的格式需要具体参考各期刊文章的要求学术论文格式&#xff0c;主要会发在月刊/季刊/年刊/不定期的刊上。 目前&#xff0c;国际著名的科技文献检索系统是SCI&#xff08;…

安卓-AndroidManifest.xml修复

解析编译之后的AndroidManifest文件格式&#xff1a;http://www.520monkey.com/archives/575 案例apk jadx打开发现AndroidManifest.xml异常&#xff0c;无法正常显示 那么我们用apktool反编译试试 apktool d APK逆向-2.apk -f可以看到报错了&#xff0c;显示不能解析此xml…