圣诞节学算法---线段树

news2025/1/15 23:19:35

线段树

快到圣诞节了,圣诞树是不是很漂亮?今天我们就来学习一下它的近亲的线段树
(话说这两玩意好像除了读音相似没啥关系)

引入

例题 1

给定一个数组 a a a 求数组中下标为 l − r l - r lr元素的和

看到这题大家都很容易想到用前缀和以 O ( n ) O(n) O(n)预处理, O ( 1 ) O(1) O(1)求解

例题 2

给定一个数组 a a a ,操作次数 ,及操作符 o p t opt opt
o p t = 1 opt=1 opt=1时 求数组中下标为 l − r l - r lr元素的和,
o p t = 2 opt=2 opt=2时 将数组中下标为 l − r l - r lr元素的+ d d d

很明显如果我们用前缀和优化,虽然查询很快,但每次修改都是 O ( r − l + 1 ) O(r-l+1) O(rl+1),当操作次数多时,仍会超时

么有没有一种查询快且修改快的东西呢?
那就是——线段树

线段树概念

“线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。”(选自百度)

有了线段树,我们就可以在 O ( l o g n ) O(log n) O(logn)的时间内进行修改和查询

模板

现附上大家最爱的模板

struct SegmentTree{
	int l,r,size;
	ll sum,tag;
	#define ls(x) (x<<1)
	#define rs(x) (x<<1|1)
}tr[MAX*4];

ll a[MAX];

inline void Push_up(int rt){
	tr[rt].sum = tr[ls(rt)].sum+tr[rs(rt)].sum;
}

void Build(int l,int r,int rt){
	tr[rt].l = l; tr[rt].r = r; tr[rt].size = r-l+1;
	if(l == r){
		tr[rt].sum = a[l];
		return;
	}
	int mid = (l+r)>>1;
	Build(l,mid,ls(rt));
	Build(mid+1,r,rs(rt));
	Push_up(rt);
}

inline void Push_down(int rt){
	if(!tr[rt].tag) return;
	tr[ls(rt)].tag += tr[rt].tag;
	tr[rs(rt)].tag += tr[rt].tag;
	tr[ls(rt)].sum += tr[rt].tag*tr[ls(rt)].size;
	tr[rs(rt)].sum += tr[rt].tag*tr[rs(rt)].size;
	tr[rt].tag = 0;
}

void Update(int rt,int l,int r,ll c){
	if(tr[rt].l >= l && tr[rt].r <= r){
		tr[rt].sum += tr[rt].size*c;
		tr[rt].tag += c;
		return;
	}
	Push_down(rt);
	int mid = (tr[rt].l+tr[rt].r)>>1;
	if(mid >= l) Update(ls(rt),l,r,c);
	if(mid <  r) Update(rs(rt),l,r,c);
	Push_up(rt);
}

ll Query(int rt,int l,int r){
	if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].sum;
	Push_down(rt);
	int mid = (tr[rt].l+tr[rt].r)>>1;
	ll cnt = 0;
	if(mid >= l) cnt += Query(ls(rt),l,r);
	if(mid <  r) cnt += Query(rs(rt),l,r);
	return cnt;
}

线段树构造

线段树
图片来源于网络

线段树的构造如其概念所示,采用二分思想,每个节点贮存范围与范围权值和,其左右子树范围分别为父节点的范围的左半段与右半段,直到范围内只剩一个元素

由于它是完全二叉树,所以我们可以用下标的2倍储存其左节点,二倍加一储存右节点

代码实现

注: r t < < 1 = r t ∗ 2 , , r t < < 1 ∣ 1 = r t ∗ 2 + 1 rt<<1 = rt * 2,,rt<<1|1 = rt * 2+1 rt<<1=rt2,rt<<1∣1=rt2+1

inline void Push_up(int rt){//将左右子树求和
	sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}
void Build(int l,int r,int rt){//l表示左边界,rb表示右边界,rt表示目前位置 
	if(l == r){
		sum[rt] = a[l];
		return;
	}
	int mid = (l+r)>>1;
	Build(l,mid,rt<<1);//左子树
	Build(mid+1,r,rt<<1|1);//右子数
	Push_up(rt);//求和
}

线段树区间查询

线段树区间查询
图片来源于网络

如图所示,线段树的查询就是不断向下找,直到节点范围在查询范围内即可

代码实现

long long Query(int nl,int nr,int l,int r,int rt){
//nl,nr为要查询的左右边界,l,r为目前查询到的左右边界.rt为目前位置
	if(l >= nl && r <= nr) return sum[rt];//如果整个在查询范围内,就不用查下去了
	int mid = (l+r)>>1;
	int ans = 0;//存和
	if(mid >= nl) ans += Query(nl,nr,l,mid,rt<<1);//查左子树
	if(mid < nr) ans += Query(nl,nr,mid+1,r,rt<<1|1);//查右子树
	return ans;
}

区间修改&懒标记

懒标记是线段树的核心

修改区间[l,r],[l,r]需要进行打懒标记的操作来减少时间消耗。
毕竟如果你不查询到这个节点这个节点也没必要一直改呀

设一个数组 tag , tag[i] 表示编号为 i 的节点的懒标记。

代码实现(以区间求和为例)

inline void Push_down(long long rt,long long l,long long r){
	if(!tag[rt]) return;
	long long mid = (l+r)>>1;
	tag[rt<<1] += tag[rt];
	tag[rt<<1|1] += tag[rt];//将懒标记传到儿子树
	sum[rt<<1] += (mid-l+1)*tag[rt];
	sum[rt<<1|1] += (r-mid)*tag[rt];//懒标记对儿子树进行修改
	tag[rt] = 0;
}
void Update(long long nl,long long nr,long long c,long long l,long long r,long long rt){
//c表示要加的数,其余同上
	if(l >= nl && r <= nr){
		sum[rt] += c*(r-l+1);
		tag[rt] += c;
		return;
	}
	int mid = (l+r)>>1;
	Push_down(rt,l,r);
	if(mid >= nl) Update(nl,nr,c,l,mid,rt<<1);
	if(mid < nr) Update(nl,nr,c,mid+1,r,rt<<1|1);
	Push_up(rt);//修改完再求和
}

至此,线段树基本就讲完了,是不是非常简单

当然线段树不只是用来求和,在其他区间问题也有广泛应用

例题 — [TJOI2018]数学计算

题目描述

小豆现在有一个数 x x x,初始值为 1 1 1。小豆有 Q Q Q 次操作,操作有两种类型:

1 m:将 x x x 变为 x × m x \times m x×m,并输出 x   m o d   M x \bmod M xmodM

2 pos:将 x x x 变为 x x x 除以第 p o s pos pos 次操作所乘的数(保证第 p o s pos pos 次操作一定为类型 1,对于每一个类型 1 的操作至多会被除一次),并输出 x   m o d   M x \bmod M xmodM

输入格式

一共有 t t t 组输入。

对于每一组输入,第一行是两个数字 Q , M Q,M Q,M

接下来 Q Q Q 行,每一行为操作类型 o p op op,操作编号或所乘的数字 m m m(保证所有的输入都是合法的)。

输出格式

对于每一个操作,输出一行,包含操作执行后的 x   m o d   M x \bmod M xmodM 的值。

样例 #1

样例输入 #1
1
10 1000000000
1 2
2 1
1 2
1 10
2 3
2 4
1 6
1 7
1 12
2 7
样例输出 #1
2
1
2
20
10
1
6
42
504
84

提示

对于 20 % 20\% 20% 的数据, 1 ≤ Q ≤ 500 1 \le Q \le 500 1Q500

对于 100 % 100\% 100% 的数据, 1 ≤ Q ≤ 1 0 5 1 \le Q \le 10^5 1Q105 t ≤ 5 , M ≤ 1 0 9 t \le 5, M \le 10^9 t5,M109 0 < m ≤ 1 0 9 0 < m \leq 10^9 0<m109

线段树维护,树顶为答案

#include<bits/stdc++.h>
#define ll long long
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((l+r)>>1)
#define lson l,mid,ls
#define rson mid+1,r,rs
using namespace std;
const int MAX = 1e5+10;
int T;
ll Q,M;
ll tree[MAX<<2];
inline void Push_up(int rt){
	tree[rt] = tree[ls]*tree[rs]%M;
}
void Build(int l,int r,int rt){
	if(l == r){
		tree[rt] = 1;
		return;
	}
	tree[rt] = 1;
	Build(lson);
	Build(rson);
}
void Update(int l,int r,int rt,int pos,int val){
	if(l == r){
		tree[rt] = (val == 0) ? 1 : val;
		return;
	}
	if(mid >= pos) Update(lson,pos,val);
	else Update(rson,pos,val);
	Push_up(rt);
}
int main(){
	ios::sync_with_stdio(false);
	cin >> T;
	while(T--){
		cin >> Q >>M;
		Build(1,Q,1);
		for(int pos = 1;pos <= Q;pos++){
			int op,m;
			cin >> op >> m;
			if(op == 1){
				Update(1,Q,1,pos,m);
				cout << tree[1]%M << "\n";
			}
			else{
				Update(1,Q,1,m,0);
				cout << tree[1]%M << "\n";
			}
		}
	}
	return 0;
}

总结

线段树,是一种维护区间和的树形结构,能在 O ( l o g n ) O(logn) O(logn)进行区间修改以及查询
以上就是基本的线段树,如果有不懂的欢迎评论区讨论

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

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

相关文章

3.2 多级放大电路的动态分析

一个 NNN 级放大电路的交流等效电路可用图3.2.1所示方框图表示。由图可知&#xff0c;放大电路中前级的输出电压就是后级的输入电压&#xff0c;即 U˙o1U˙i2\dot U_{o1}\dot U_{i2}U˙o1​U˙i2​、U˙o2U˙i3\dot U_{o2}\dot U_{i3}U˙o2​U˙i3​、⋯\cdots⋯、U˙o(N−1)U…

react笔记_07 hooks

什么是hook? 以前我们称函数组件为简单组件&#xff0c;因为函数组件是无状态的(没有state)。 而在React 16.8版本增加了 Hook&#xff0c;它可以让你在不编写 class 组件的情况下&#xff0c;也就是我们可以在函数组件中使用 state 以及其他的 React 特性。 Hook 不能在 c…

5G无线技术基础自学系列 | 5G服务完整性KPI

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 5G服务完整性KPI用来评估5G RAN中终端用…

java: 无效的目标发行版: 17 新建springBoot项目

问题 java: 无效的目标发行版: 17 详细问题 新建springBoot项目&#xff0c;对数据库配置后启动项目&#xff0c;控制台报错 java: 无效的目标发行版: 17 如下图 解决方案 查看JDK版本 &#xff08;事实上&#xff0c; 该步骤查看本机的已配置环境变量的JDK版本&#xff…

Python基础(十)模块与包

目录 1. 简介 1.1 模块 1.2 包 2. 使用 2.1 创建 2.2 引用 1. 简介 1.1 模块 Python 中一个以 .py 结尾的文件就是一个模块&#xff0c;模块中定义了变量、函数等来实现一些类似的功能。Python 有很多自带的模块&#xff08;标准库&#xff09;和第三方模块&#xff0c…

UMC产品UI升级说明

随着产品功能的逐渐完善&#xff0c;一款好的产品需要不断地打磨才能变得更完整、更稳定。所以&#xff0c;UMC作为数通畅联的核心产品&#xff0c;为了满足更多的需求&#xff0c;更好的视觉效果和体验感&#xff0c;一直都在不断地完善迭代。 本次升级主要是针对整体页面进行…

实现股票交易c接口​​​​​​​需要的注意事项有哪些?

实现股票交易c接口需要的注意事项有哪些&#xff1f;最近有很多朋友问小编这个问题&#xff0c;小编今天就说说&#xff01; 在基类列表中包含接口名称 为每一个接口的成员提供实现 如果类从基类继承并实现了接口&#xff0c;基类列表中的基类名称必须放在所有接口之前。(一个…

PMAC的PVT功能实现解析笔记

从上图中我们可以得到如下信息&#xff1a; 速度截面是一个抛物线 P0P_0P0​、V0V_0V0​是上一次指定的&#xff0c;P1P_1P1​、V1V_1V1​是当前期望的&#xff0c;TA是当前期望的运动时间 A0A_0A0​是上一次计算的&#xff0c;A1A_1A1​是当前计算的&#xff0c;加加速度dA/…

使用 x-sheet 构建在线疫情高峰预测数据表

背景 最近&#xff0c;一位大数据专家通过百度“发烧”的搜索指数、公开的疫情感染人数等指标&#xff0c;计算出每个城市的“超额发烧搜索指数累计面积”&#xff0c;并且通过城市的搜索指数累计增长、累计速度&#xff0c;就可以算出现在每一个有疫情的城市疫情大概的达峰时…

MyBatis-Plus保姆级快速上手教程

为简化开发而生 Mybatis简化JDBC操作 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 1、特性 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对…

都28了,半路转行学编程还来得及吗?

很多新来的粉丝&#xff0c;经常会问到&#xff1a;“我现在xx岁了&#xff0c;学编程晚吗?"&#xff0c;“程序员是不是吃青春饭啊&#xff0c;我都没有青春了&#xff0c;还能找到工作吗&#xff1f;”... 其实这类的问题&#xff0c;我以前都已经整理过文章&#xff0…

正点原子-Linux嵌入式开发学习-第二期05

第十三讲&#xff1a;按键实验 1&#xff1a;原理图分析&#xff0c;按键按下低电平进入&#xff0c;不按下高电平 对应的端口为&#xff1a;GPIO1_IO18 复制上一次工程&#xff0c;记得make clean清除上一次的文件 程序编写 1&#xff1a;新建key文件夹并新建bsp_key.c和bsp_…

Scala 高阶函数(二)

一、scala自带高阶函数 概述&#xff1a;可以接收一个函数的函数就是高阶函数&#xff0c;又称为算子 1。map&#xff1a; 将集合中的每一个元素通过指定功能(函数)映射(转换)成新的结果集 val list1 List(1,2,3) val list2 list1.map(v>v*2) println(list2) //List…

[第十二届蓝桥杯/java/算法]D——相乘

&#x1f9d1;‍&#x1f393;个人介绍&#xff1a;大二软件生&#xff0c;现学JAVA、Linux、MySQL、算法 &#x1f4bb;博客主页&#xff1a;渡过晚枫渡过晚枫 &#x1f453;系列专栏&#xff1a;[编程神域 C语言]&#xff0c;[java/初学者]&#xff0c;[蓝桥杯] &#x1f4d6…

C#获取计算机硬件的参数信息

2019年的时候用PowerBuilder写过一个计算机信息收集小程序&#xff0c;《计算机信息收集小程序》&#xff0c;当时是查注册表来实现的。 还可以通过对windows的API调用来做&#xff0c;这个稍微有点麻烦。 使用C#也可以通过三种方式来实现&#xff0c;分别是读取注册表、window…

import语句写烦了,怎么办?

每次写数据相关的代码时都会习惯性地先写一堆import语句&#xff0c;不管用得到用不到&#xff0c;先在文首默一遍再说。 或者&#xff1a; ❞虽说CtrlC和CtrlV也很方便&#xff0c;但是每次都要先“抄一次”也很烦。 那么有没有什么好的解决办法&#xff1f; Python有一个模…

《码出高效:java开发手册》六-数据结构与集合(一)

前言 本章主要是讲数据结构与集合&#xff0c;这章内容涉及到非常基础的知识&#xff0c;内容相对较多&#xff0c;首先从数组讲起&#xff0c;引申到集合框架&#xff0c;之后再到集合源码&#xff0c;最后介绍了高并发集合框架 集合 集合在代码中是collection&#xff0c;…

智牛股_第8章_Sentinel

智牛股_第8章_Sentinel 文章目录智牛股_第8章_Sentinel学习目标第1章 Sentinel集成使用1. 目标2. 步骤3. 实现3.1 生产环境最优配置方案3.2 用户服务集成3.3 熔断规则配置3.4 启动Sentinel监控台3.5 功能使用验证4. 总结第2章 用户注册功能1. 目标2. 步骤3. 实现3.1 用户注册流…

flstudio21版本有什么新功能及免费新插件

全能数字音乐工作站&#xff08;DAW&#xff09;编曲、剪辑、录音、混音&#xff0c;23余年的技术积淀和实力研发&#xff0c;FL Studio已经从电音领域破圈&#xff0c;成功蜕变为瞩目的全能DAW&#xff0c;把电脑变成全功能音乐工作室&#xff0c;接下来我们会为您一一展示 2…

Prometheus Operator实战—— Prometheus、Alertmanager、Grafana 监控RockectMq

1. RocketMQ 介绍 RocketMQ 是一个分布式消息和流数据平台&#xff0c;具有低延迟、高性能、高可靠性、万亿级容量和灵活的可扩展性。简单的来说&#xff0c;它由 Broker 服务器和客户端两部分组成&#xff0c;其中客户端一个是消息发布者客户端(Producer)&#xff0c;它负责向…