线段树--RMQ问题

news2024/11/13 14:30:07

线段树

  • 由来
  • 算法讲解分析
    • 树的数据结构
    • 结点
    • 四个基本操作
  • 例题
    • 天才的记忆
    • 最大数

由来

线段树是RMQ区间最值问题的一种解题方法,在给出的区间是静态不变的时候,可以使用ST算法进行离线查询某个区间的最值,先预处理后进行m次查询,时间复杂度为O(nlogn+m* 1)=O(nlogn),详情参考我的这篇博客:RMQ问题
但是当区间是不断变化的,在区间变化的同时进行询问的情况ST算法时间复杂度会达到O(m*nlogn)每次区间变化后都要重新进行预处理,不适用,此时就需要用到线段树

算法讲解分析

树的数据结构

线段树是一个完美的完全满二叉树(除了最后一层其他层都是慢得,并且除了叶子结点外每个结点都既有左孩子也有右孩子)
那么对于这个树的存储就可以用堆的方式来存储,即用一维数组存树
对于树中的每个结点编号为x,其父节点编号为x/2(x>>1),左儿子为2x(x<<1),右儿子为2*x+1(x<<1|1),当然需要满足根节点编号从1开始,如果从0开始就会比较麻烦。

结点

树的每个结点对应一个区间,根节点表示所有元素所在区间,结点的左孩子表示该区间的左半部分,右孩子表示该区间的右半部分,结点存储所表示区间的左端点和右端点,以及该区间的最值。定义结构体如下:

typedef struct Node
	{
	    int l;int r;int Max;
	}T;

用一个图来表示结点间的关系,假设给定的元素区间为[1,10]一共十个元素

在这里插入图片描述
考虑一共大致会有多少个结点,以便于决定开多大的数组空间来存树,倒数第二层最坏的情况下大概是n个,每个区间大小都为1,因此,除去最后一层,其他层的所有结点个数为2n-1(n+n/2+n/4+…+1等比数列前n项和),最后一层最多为2n个,因此整棵树的结点个数最多为2n-1+2n,大致为4n个,因此数组开4n就够了。

四个基本操作

pushup(id): 用子节点信息更新父节点的信息
在这里插入图片描述
代码:用左右孩子的结点值更新父节点的值

void pushup(int id)
{
	tree[id].Max=max(tree[id*2].Max,tree[id*2+1].Max);  //父节点的最大值取左右孩子的最大值 
}

build(id,l,r): 建树,将一段区间初始化为一棵线段树,如果一开始的区间为空,在后续才逐渐向区间内添加元素的话就先建立一棵空的树,只需要表达结点之间的关联即可
代码:

void build(int id,int l,int r)    //建立左端点为l,右端点为r的区间对应的结点,结点编号为id
{
	tree[id].l=l;
	tree[id].r=r;
	if(l==r)  
	{
		//tree[id].Max=num;   如果是空树就不需要给叶子结点赋值
		return ;  //区间长度为1,只剩一个元素了
	}	 
	int mid=l+r>>1;
	build(2*id,l,mid);       //建立左子树
	build(2*id+1,mid+1,r);   //建立右子树
	//pushup(id);       如果最开始建立的是一个空壳树就不需要向上传递信息 
} 

query(id,l,r): 查询[l,r]区间信息,比如求这个区间的最值
查询和修改都涉及到定位到某个元素或者某段区间的问题,二者的解决思路是一样的,并且定位到某个元素实际也可以看成是一段只有一个元素的区间,因此,只需要实现在总区间内定位某个区间的最值就行
假设[l,r]为待查区间,[tr,tl]为当前树结点对应的区间,从根节点不断递归往下搜索待查区间内的最值,待查区间和当前树结点对应的区间有以下两种对应关系:
在这里插入图片描述
线段树在区间比较短时的查询是比较慢的,但是总体查询和修改过程中访问过的区间(结点)数量一定在logn范围内,大致为4logn(树状数组只有logn)

modify(id): 修改树,修改某个结点或者修改某个区间的值
修改单点值:modify(id,x,v) 修改x位置元素为v,直接递归往下找到对应的点,回溯的时候更新一下父节点的值即可,easy
代码:

void modify(int id,int n,LL v)  //修改单点数,将第n位的值改为v 
{
	int tl=tree[id].l,tr=tree[id].r;
	if(tl==tr)  //定位到要修改的点了,区间大小为1,此时tl=tr=n 
	{
		tree[id].Max=v; return ;
	} 
	int mid=tl+tr>>1;
	if(n<=mid)  modify(2*id,n,v); //跟结点左孩子(区间左边)有交集,修改点属于左区间 
	else if(mid<n)   modify(2*id+1,n,v);    //跟结点右孩子(区间右边)有交集 ,修改点属于右区间
	pushup(id);    //修改子树后向上更新一下最最值 
}

修改区间值:pushdown操作延迟更新,将父节点的信息下传到子节点(懒标记、延迟标记),区间内所有元素加上一个数,递归将左右区间分别加上这个数

例题

天才的记忆

题目链接: https://www.acwing.com/problem/content/description/1275/
思路分析: 这个题是标准的RMQ问题,并且是数组固定的(离线做法)情况,因此用ST算法会更快一些,时间复杂度能控制在O(nlogn预处理+m查询),用线段树有点大材小用了,时间复杂度为O(nlogn建树+mlogn单次操作),只需要按照模板将树建立起来,然后做查询即可
AC代码:

#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;
const int N=200005;
typedef struct Node
{
    int l;int r;int Max;
}T;
T tree[4*N];
int num[N];
int n,m,x,y;
void pushup(int id)
{
    tree[id].Max=max(tree[id*2].Max,tree[id*2+1].Max);
}
void build(int id,int l,int r)
{
    tree[id].l=l;
    tree[id].r=r;
    if(l==r)
    {
        tree[id].Max=num[l];return ;
    }
    int mid=l+r>>1;
    build(2*id,l,mid);
    build(2*id+1,mid+1,r);
    pushup(id);
}

int query(int id,int l,int r)
{
    int tl=tree[id].l,tr=tree[id].r;
    if(tl>=l&&tr<=r)  return tree[id].Max;
    
    int mid=tl+tr>>1;
    int ans=-0x3f3f3f3f;
    if(l<=mid) ans=query(2*id,l,r);
    if(r>mid) ans=max(ans,query(2*id+1,l,r));
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
    }
    scanf("%d",&m);
    build(1,1,n);
    while(m--)
    {
        scanf("%d %d",&x,&y);
        printf("%d\n",query(1,x,y));
    }
    return 0;
}

最大数

题目链接: https://www.acwing.com/problem/content/description/1277/
思路分析: 这个题也是区间最值问题,但是如果用ST算法,每次往区间内插入数都需要对dp数组进行初始化,导致时间复杂度为O(mnlogn)了,肯定会超时,因此只能采用线段树的在线做法。
不能把往数组尾部插入数看做是区间在不断地变化,这样的话每次插入一个数都需要重新build树,就没有实现线段树做法,导致超时,因为build的时间复杂度为O(nlogn)。实际一开始将数组看做是有数的,只不过取值为0或者负无穷,每次往数组末尾插入一个数,看做是修改区间内第n个数的值,线段树的修改和查询时间复杂度能保证在O(4logn)内,因此总的时间复杂度保证为O(mlogn)。
一开始就用所有元素初始化树,但是在这道题中一开始的区间元素没给出来,表面看是空的,实际上我们当做是0,区间大小考虑最坏m次都是插入操作,则区间为[1,m]。
AC代码:

#include<iostream>
#include<stdio.h>
using namespace std;
typedef long long LL;
const int N=200005;
typedef struct Node
{
    int l;int r;LL Max;
}T;
T tree[4*N];
LL p;   //值要开long long  
int m;    //区间的总长度最大为m(所有m次操作都是插入数操作) 
void pushup(int id)
{
	tree[id].Max=max(tree[id*2].Max,tree[id*2+1].Max);  //父节点的最大值取左右孩子的最大值 
} 
void build(int id,int l,int r)
{
	tree[id].l=l;
	tree[id].r=r;
	if(l==r)  return ;
	int mid=l+r>>1;
	build(2*id,l,mid);       //建立左子树
	build(2*id+1,mid+1,r);   //建立右子树
	//pushup(id);       如果最开始建立的是一个空壳树就不需要向上传递信息 
} 

LL query(int id,int l,int r)  //查询[l,r]区间内的最值 
{
	int tl=tree[id].l,tr=tree[id].r;
	if(tl>=l&&tr<=r)  //[tl,tr]区间属于[l,r]区间,直接返回
	{
		return tree[id].Max;
	} 
	int mid=tl+tr>>1;
//	if(l<=mid)  return query(2*id,l,r); //跟结点左孩子(区间左边)有交集 
//	else if(mid<=r)  return query(2*id+1,l,r);    //跟结点右孩子(区间右边)有交集
//  上面这种写法是错的,如果同时跟左边和右边都有交集,只会在查询了左边后就返回了 
	LL ans=0;   
	if(l<=mid)  ans=query(2*id,l,r); //跟结点左孩子(区间左边)有交集 
	if(mid<r)  ans=max(ans,query(2*id+1,l,r));    //跟结点右孩子(区间右边)有交集  
	return ans;
}

void modify(int id,int n,LL v)  //修改单点数,将第n位的值改为v 
{
	int tl=tree[id].l,tr=tree[id].r;
	if(tl==tr)  //定位到要修改的点了,区间大小为1,此时tl=tr=n 
	{
		tree[id].Max=v; return ;
	} 
	int mid=tl+tr>>1;
	if(n<=mid)  modify(2*id,n,v); //跟结点左孩子(区间左边)有交集,修改点属于左区间 
	else if(mid<n)   modify(2*id+1,n,v);    //跟结点右孩子(区间右边)有交集 ,修改点属于右区间
	pushup(id);    //修改子树后向上更新一下最最值 
}

int main()
{
    scanf("%d %ld",&m,&p);
    build(1,1,m); 
    int n=0,t=0;
    LL a=0;
    while(m--)
    {
    	getchar();
        char c;scanf("%c %d",&c,&t);
        if(c=='Q') //查询操作
		{
			a=query(1,n-t+1,n); 
			printf("%d\n",a); //待查询区间 
		} 
		else    
		{
			n++; //更新插入数的位置 
			modify(1,n,(t+a)%p);   //修改线段树 
		}
    }
    return 0;
}

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

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

相关文章

9. QML_OpenGL--2. 在QQuick中搭建加载OpenGL框架

1. 说明&#xff1a; OPenGL一般在 QtWidget 中使用&#xff0c;但目前使用 QML 做界面开发是一种趋势&#xff0c;同时在QML中使用OPenGL进行渲染也是十分必要&#xff0c;文章简单介绍如何在QML中使用 OPenGL&#xff0c;搭建了一种基本的框架。整体思路和在 QtWidget 中类似…

RabbitMQ学习(四):消息应答

一、消息应答的概念消费者完成一个任务可能需要一段时间&#xff0c;如果其中一个消费者处理一个长的任务并仅只完成 了部分突然它挂掉了&#xff0c;会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息&#xff0c;便立即将该消 息标记为删除。在这种情况下&#xff0c;突然…

C++中引用的本质以及与指针的区别(c++数据在内存中的分配)

1、引用的意义 引用作为变量别名而存在&#xff0c;因此在一些场合可以替代指针&#xff0c;引用相对于指针来说具有更好的可读性和实用性 // swap函数的实现对比 #include <iostream> using namespace std;void swap1(int a, int b); void swap2(int *p1, int *p2); v…

【数据结构】---顺序表的实现

最近学校开始学习数据结构了&#xff0c;没事就手搓一个顺序表。&#x1f308;线性表线性表是n个具有相同特性的数据元素的有限序列&#xff0c;是一种实际中广泛使用的数据结构&#xff0c;常见的线性表有顺序表、链表、栈、队列、字符串。线性表在逻辑上是线性结构&#xff0…

【C语言学习笔记】:静态库

一、什么是库 库是写好的现有的&#xff0c;成熟的&#xff0c;可以复用的代码。现实中每个程序都要依赖很多基础的底层库&#xff0c;不可能每个人的代码都从零开始&#xff0c;因此库的存在意义非同寻常。 本质上来说库是一种可执行代码的二进制形式&#xff0c;可以被操作…

基于”PLUS模型+“生态系统服务多情景模拟预测实践技术应用

生态系统服务是人类直接或间接从生态系统中获得的惠益&#xff0c;在应对城市挑战和实施可持续发展方面发挥着至关重要的作用。随着全球城市化的快速发展, 频繁的人类活动导致了土地利用的快速变化&#xff0c;导致生态系统结构和功能的变化&#xff0c;影响生态系统服务的供应…

【Nginx】Docker配置ngnix,实现同服务器ip多站点多域名

Docker配置ngnix&#xff0c;实现同服务器ip&#xff0c;多域名映射多站点 本文首发于 慕雪的寒舍 1.说明 一般情况下&#xff0c;我们的域名映射到ip后&#xff0c;默认访问的是80端口。如果你的服务器只部署了一个服务&#xff0c;这样也是够用的。 但是很多项目对性能的占…

CAN总线详细介绍

1.1 CAN是什么&#xff1f; CAN 最终成为国际标准 &#xff08; ISO11898(高速应用)和 ISO11519&#xff08;低速应用&#xff09;&#xff09;&#xff0c;是国际上应用最广泛的现场总线之一。 1.2 CAN总线特点 多主方式: 可以多主方式工作&#xff0c;网络上任意一个节点…

前端学习第一阶段——第五章(上)

5-1 CSS基本选择器 01-CSS层叠样式表导读 02-CSS简介 03-体验CSS语法规范 04-CSS代码风格 05-CSS选择器的作用 06-标签选择器 07-类选择器 08-使用类选择器画盒子 09-类选择器特殊使用-多类名 10-id选择器 11-通配符选择器 5-2 CSS样式 12-font-family设置字体系列 13-font-s…

商场技术点-3

1.后端服务校验 1.1JSR-303介绍 JSR是Java Specification Requests的缩写&#xff0c;意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR&#xff0c;以向Java平台增添新的API和服务。JSR已成为Java界的一个…

springboot项目配置文件加密

1背景&#xff1a; springboot项目中要求不能采用明文密码&#xff0c;故采用配置文件加密. 目前采用有密码的有redis nacos rabbitmq mysql 这些配置文件 2技术 2.1 redis nacos rabbitmq 配置文件加密 采用加密方式是jasypt 加密 2.1.1 加密步骤 2.1.2 引入maven依赖 …

Android进阶之路 - StringUtils、NumberUtils 场景源码

忘记是在去年还是前年的时候遇到一个需要检测所传字符串是否为数字的场景&#xff0c;开始使用 NumberUtils.isNumber() 提示错误 &#xff0c;没有解决问题&#xff08;可能是因为依赖版本导致&#xff09;&#xff0c;最后使用的是StringUtils.isNumeric()&#xff0c;当时关…

剑指 Offer 43. 1~n 整数中 1 出现的次数

题目 输入一个整数 n &#xff0c;求1&#xff5e;n这n个整数的十进制表示中1出现的次数。 例如&#xff0c;输入12&#xff0c;1&#xff5e;12这些整数中包含1 的数字有1、10、11和12&#xff0c;1一共出现了5次。 思路 要求出小于等于 n 的非负整数中数字 1 出现的个数…

Prometheus系列(五)grafana web 配置邮件告警

目录 1. contact points&#xff08;创建告警渠道&#xff09; 2. Notification policies&#xff08;创建告警通道匹配规则&#xff09; 3. Alert rules&#xff08;配置告警策略&#xff09; 告警配置 告警页面名词解释&#xff1a; 1. contact points&#xff08;创建告…

玩转数据结构之Java实现线段树

前言 线段树是一种二叉搜索树&#xff0c;线段树的每个结点都存储了一个区间&#xff0c;也可以理解成一个线段&#xff0c;在这些线段上进行搜索操作得到你想要的答案。 线段树的适用范围很广&#xff0c;可以在线维护修改以及查询区间上的最值&#xff0c;求和。更可以扩充到…

一文浅谈sql中的 in与not in,exists与not exists的区别以及性能分析

文章目录1. 文章引言2. 查询对比2.1 in和exists2.2 not in 和not exists2.3 in 与 的区别3. 性能分析3.1 in和exists3.2 NOT IN 与NOT EXISTS4. 重要总结1. 文章引言 我们在工作的过程中&#xff0c;经常使用in&#xff0c;not in&#xff0c;exists&#xff0c;not exists来…

Unity2018.4.x~2021.3.x版 Android资源处理

注意&#xff1a;本文都是针对使用Gradle编译从Unity2018.4.x到Unity2020.3.x都是可以直接将Android的适配资源直接放到${PROJECT_PATH}/Assets/Plugins/Android/对应的目录下的&#xff0c;如&#xff1a;在此目录下可以方安卓平台对应的assets、res目录及子目录资源&#xff…

前端的CSS样式表知识提要

文章目录前言基本概念屏幕尺寸屏幕分辨率屏幕像素密度/像素密度/屏幕密度视口和浏览器窗口长度单位&#xff1a;px、em/rem和vhCSS属性的继承与覆盖CSS选择器CSS 布局基础盒子模型绝对定位和相对定位display属性浮动正常布局流&#xff08;normal flow&#xff09;Flexbox 布局…

【Python表白代码】 2.14“Valentine‘s Day”“没别的意思 就是借着特殊日子说声喜欢你”你在哪儿?我去见你~(各种玫瑰源码合集)

导语 Valentines Day Every man is a poet when he is in love 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 哈喽&#xff01;我是你们的木木子吖~ 情人节又到了&#xff0c;礼物备好了没&am…

k8s部署Prometheus+Grafana

1.prometheus简介 Prometheus是一个开源的系统监控和警报工具包&#xff0c;最初由SoundCloud开发的&#xff0c;社区活跃&#xff0c;2016年加入了云原生计算基金会成为继Kubernetes之后的第二个托管项目&#xff1b;普罗米修斯以时间序列数据的形式收集并存储度量值&#xff…