RMQ--区间最值问题(在更)

news2024/12/28 20:50:40

RMQ(Range Minimum/Maximum Query)

  • RMQ解决的问题
  • ST算法 O(nlogn)
  • 线段树
  • 例题
    • 数列区间最大值
    • 最敏捷的机器人
    • 天才的记忆
    • Frequent values
  • 总结(ST和线段树对比)

RMQ解决的问题

RMQ是一个解决多个区间最值查询的算法,即区间最值查询;
如果我们要查询某一个区间内的最值,显然我们可以用暴力搜索的方式在O(n)的时间复杂度内获得结果,但是当我们要查询10000个不同区间内的最值时,我们如果依然采用暴力搜索的方式,那么时间复杂度就会变成O(mn),其中m指的是查询次数,n指的是数据个数。
RMQ算法的目标就是降低多区间最值查询问题的时间复杂度,一般还可以使用线段树求解,复杂度是O(mlogn) 。但还有一种更简便的ST算法,预处理复杂度是O(nlogn),查询O(1)。

RMQ四种解法

ST算法 O(nlogn)

ST(Sparse Table)即稀疏表,算法是通过动态规划思想实现的,他需要在O(nlogn)的时间复杂度内预处理出部分区间的最值,在O(1)的时间内获得一个区间的最值查询结果。总的时间复杂度被降到了O(nlogn)。

预处理 O(nlogn)
dp[i][j]表示数组第i个元素开始,长度为2^j的区间内的最值。
根据这种dp定义方式,我们可以轻松获得dp[i][j]的递推公式:
  dp[i][j]=max(dp[i][j-1],dp[i+2^(j-1)][j-1]);
原理类似倍增,首先比较每2个元素的最值,然后再通过比较这2个最值,得到4个元素的最值,以此类推8个、16个……不断地枚举区间长度,,对每种区间长度求出所有不同起点的区间的最值。
  在这里插入图片描述
代码实现:

void ST()
{
	//下标i最好是从1开始 
	for(int i=1;i<=n;i++) dp[i][0]=a[i];   //区间长度为1时最值就是自己 
	//int t=(int)(log(n) / log(2)); j<=t也可以 
	for(int j=1;pow(2,j)<=n;j++)   //枚举区间的长度 ,必须先遍历j再遍历i 
	{
		for(int i=1;i+pow(2,j)-1<=n;i++)  //保证左右合并后的区间的r不超过最后下标n 
		{
			dp[i][j]=max(dp[i][j-1],dp[i+pow(2,j-1)][j-1]);
		}
	} 
}

void ST()
{
	for(int i=1;i<=n;i++) dp[i][0]=a[i];  
	for(int j=1;1<<j<=n;j++)    
	{
		for(int i=1;i+1<<j-1<=n;i++)  //用左移的方式更快一些 
		{
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
		}
	} 
}

查询 O(1)
预处理算法的每个区间除了第一个以外都是偶数,那万一他给个不符合条件的区间怎么办,也就是显然我们似乎不能直接一步从dp数组中得到问题的答案,这里我们采用的是从两个有重叠的区间中找到我们需要的答案,这两个重叠的区间一定会包含[l,r]区间内的所有元素
我们需要先算出不超过这个区间长度的 2^ t的t的最大值:log2(r−l+1) 。
那么这个区间的最大值就为 “从l开始的2^ t 个数” 和 “以r结尾的2^ t个数” 这两段的最大值较大的一个。即 max(f[l][t], f[r-(1<<t)+1][t])。
2^t是不超过区间长度r-l+1的最大的数,那2 ^(t+1)一定是超过r-l+1的,也就是前2 ^t个数和后2 ^t个数的和2 ^(t+1)个数的长度一定是大于等于区间长度r-l+1的,因此前后区间能够囊括这个区间内的所有数
在这里插入图片描述
代码实现:

//查询
LL query(LL l,LL r)
{
    LL t=log2(r-l+1);
    return max(dp[l][t],dp[r-(1<<t)+1][t]);
}

线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。

例题

数列区间最大值

题目链接:https://www.acwing.com/problem/content/1272/
ST算法
思路分析:直接暴力求,明显的,最坏情况下时间复杂度为O(nm)=10^11,肯定超时
用ST算法来求,预处理O(nlogn)=10^7,查询O(m*1)=10 ^6不会超时
暴力超时代码:

#include<iostream>
using namespace std;
const int N=100005;
int a[N];
int n,m,x,y;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=0;i<m;i++)
    {
        scanf("%d %d",&x,&y);
        int max=0;
        for(int j=x;j<=y;j++)
        {
            if(a[j]>max) max=a[j];
        }
        printf("%d\n",max);
    }
    return 0;
}

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N=100005;
LL a[N];
LL n,m,x,y;
LL dp[N][20];  //2^20就足够大于n了

//预处理
void ST()
{
    for(LL i=1;i<=n;i++) dp[i][0]=a[i];
    for(LL j=1;1<<j<=n;j++)
    {
        for(LL i=1;i+(1<<j)-1<=n;i++)
        {
            dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
        }
    }
}
//查询
LL query(LL l,LL r)
{
    LL t=log2(r-l+1);
    return max(dp[l][t],dp[r-(1<<t)+1][t]);
}
int main()
{
    scanf("%d %d",&n,&m);
    for(LL i=1;i<=n;i++)  scanf("%d",&a[i]);
    ST();
    while(m--)
    {
        scanf("%d %d",&x,&y);
        printf("%d\n",query(x,y));
    }
    return 0;
}

线段树算法
思路分析:
AC代码:

最敏捷的机器人

题目链接:https://www.acwing.com/problem/content/description/1273/
思路分析:就维护最大值和最小值数组即可
AC代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
typedef long long LL;
LL a[N];
LL Max[N][20];  // 最大值
LL Min[N][20];  //最小值
int n,k;
int main()
{
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++) 
    {
        scanf("%ld",&a[i]);
        Max[i][0]=a[i];
        Min[i][0]=a[i];
    }
    
    for(int j=1;1<<j<=n;j++)
    {
        for(int i=1;i+(1<<(j-1))-1<=n;i++)
        {
            Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]);
            Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
        }
    }
    int t=n-k+1;
    for(int i=1;i<=t;i++)
    {
        int l=i,r=i+k-1;
        int tmp=log2(k);
        printf("%d ",max(Max[l][tmp],Max[r-(1<<tmp)+1][tmp]));
        printf("%d\n",min(Min[l][tmp],Min[r-(1<<tmp)+1][tmp]));
    }
    return 0;
}

天才的记忆

题目链接:https://www.acwing.com/activity/content/problem/content/1795/
ST算法
跟上面两个题代码一样
线段树算法

Frequent values

题目链接:http://poj.org/problem?id=3368
ST算法
思路分析:首先需要对给出的区间进行一下处理,将连续的元素进行连续标号,对于标号后的数组,任意给出一个区间,可以将这个区间看做由两部分组成,前面部分是前一个连续数组遗留下来的一些元素,后面部分是多个属于区间内的连续元素组成的数组,最后取遗留下来的元素个数和后面多个连续数的最大的数量二者的最大值即可,也就是先找到这两部分的分界点:区间[i,j]内的第一个1的位置为k,答案就为max(k - i,RMQ(k,j));
在这里插入图片描述
AC代码:

#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;
const int N=100005;
int a[N];
int dp[N][20];
int n,q,t,pre,x,y;
void ST()
{
	for(int i=1;i<=n;i++) dp[i][0]=a[i];
	
	for(int j=1;1<<j<=n;j++)
	{
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
		}
	}
} 
int query(int l,int r)
{
	t=log2(r-l+1);
	return max(dp[l][t],dp[r-(1<<t)+1][t]);
}
int main()
{
	while(scanf("%d", &n) && n != 0)
	{
		scanf("%d",&q);
		int pre=-100001;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&t);
			if(t==pre) a[i]=a[i-1]+1;
			else a[i]=1;
			pre=t;
		}
		ST();
		while(q--)
		{
			scanf("%d %d",&x,&y);
			int l=-1;
			for(int i=x;i<=y;i++)
			{
				if(a[i]==1) 
				{
					l=i;
					break;
				}  //找到第一个1作为区间左部 
			}
			if(l!=-1) printf("%d\n",max(l-x,query(l,y)));   //区间内有1 
			else printf("%d\n",y-x+1);      //区间内没有1 
		}
	}
	return 0;
}

/*
10 4
 1  2 3 4 5 6 7  8  9 10  //下标 
-1 -1 1 1 1 1 3 10 10 10  //所给数组 
 1  2 1 2 3 4 1 1  2  3   //连续标号后的数组 
2 3 --1     待求区间内只有一组连续重复值,前面有上一组剩下的一个元素 
1 10  -- 4  待求区间内有多组连续重复值,前面没有上一个组剩下的一些元素 
5 10  --3   待求区间内有多组连续重复值,前面有上一组剩下的两个元素
4 6   --3   待求区间内没有新的连续重复值,只有前面上一组剩下的元素 
0
*/

线段树算法

总结(ST和线段树对比)

当题目是离线的时侯使用ST算法更快,时间复杂度为O(nlogn),当题目是在线的时候直接使用线段树维护即可,因为线段树可以进行修改,单点修改维护也是logn,总的时间复杂度为O(nlogn+mlogn)

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

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

相关文章

MySQL 创建数据表

在创建数据库之后&#xff0c;接下来就要在数据库中创建数据表。所谓创建数据表&#xff0c;指的是在已经创建的数据库中建立新表。 创建数据表的过程是规定数据列的属性的过程&#xff0c;同时也是实施数据完整性&#xff08;包括实体完整性、引用完整性和域完整性&#xff09…

LwIP系列--线程通信消息结构

一、目的如果有小伙伴移植过LwIP&#xff0c;那么你肯定知道在LwIP源码中tcp/ip协议栈是作为一个单独的线程运行的&#xff0c;那么就有这样一个问题&#xff0c;我们从mac外设上收到的以太网数据包是如何交给tcp/ip线程进行处理的&#xff0c;用户发送的数据又是如何经过协议栈…

不学Python迟早会被淘汰?Python真有这么好的前景?

最近几年Python编程语言在国内引起不小的轰动&#xff0c;有超越Java之势&#xff0c;本来在美国这个编程语言就是最火的&#xff0c;应用的非常非常的广泛&#xff0c;而Python的整体语言难度来讲又比Java简单的很多。尤其是在运维的应用中非常的广泛&#xff0c;所以之前出了…

Ubuntu20.04无线网卡驱动安装

文章目录一.未安装无线网卡驱动的Ubuntu20.04联网方式二.Ubuntu20.04无线网卡驱动安装UbuntuU盘启动盘安装好Ubuntu 20.04之后&#xff0c;发现没有无线网络&#xff0c;不过有线可以用。一.未安装无线网卡驱动的Ubuntu20.04联网方式 比较简单的就是直接拉一条网线进行连接&am…

【C语言】宏定义 结构体 枚举变量的用法

目录 一、数据类型 二、C语言宏定义 三、C语言typedef重命名 四、 #define与typedef的区别 五、结构体 六、枚举变量 补充学习一点STM32的必备基础知识 一、数据类型 二、C语言宏定义 关键字&#xff1a;#define 用途&#xff1a;用一个字符串代替一个数字&#xff0c;…

214 情人节来袭,电视剧 《点燃我温暖你》李峋同款 Python爱心表白代码,赶紧拿去用吧

大家好&#xff0c;我是徐公&#xff0c;六年大厂程序员经验&#xff0c;今天为大家带来的是动态心形代码&#xff0c;电视剧 《点燃我温暖你》同款的&#xff0c;大家赶紧看看&#xff0c;拿去向你心仪的对象表白吧&#xff0c;下面说一下灵感来源。 灵感来源 今天&#xff…

2023-02-10 - 6 聚合

当用户使用搜索引擎完成搜索后&#xff0c;在展示结果中需要进行进一步的筛选&#xff0c;而筛选的维度需要根据当前的搜索结果进行汇总&#xff0c;这就用到了聚合技术。聚合的需求在很多应用程序中都有所体现&#xff0c;例如在京东App中搜索“咸鸭蛋”&#xff0c;然后单击搜…

简述操作系统的系统中断

系统中断 系统中断是指 CPU 对系统发生的某个事件做出的一种反应&#xff1a;CPU 暂停正在执行的程序&#xff0c;保留现场后转去执行相应的处理程序&#xff0c;处理完该事件后再返回断点继续执行被打断的程序。 中断可以避免 CPU 轮询等待某条件成立&#xff0c;减小系统开…

2023-02-10 - 5 文本搜索

与其他需要精确匹配的数据不同&#xff0c;文本数据在前期的索引构建和搜索环节都需要进行额外的处理&#xff0c;并且在匹配环节还要进行相关性分数计算。本章将详细介绍文本搜索的相关知识。 本章首先从总体上介绍文本的索引建立过程和搜索过程&#xff0c;然后介绍分析器的…

离子阱量子计算机

1.新闻 2020年6月&#xff0c;科技制造企业霍尼韦尔&#xff08;Honeywell&#xff09;发布第一台离子阱量子计算机H0&#xff0c;它拥有64量子体积&#xff0c;它是IBM和谷歌同时期量子计算机的两倍。公司表示之所以能取得这一成就缘于他们2015年在一篇科学论文中展示的量子电…

Docker 容器与容器云读书笔记(一)

最近都没时间看书&#xff0c;闲暇之余看看书&#xff0c;写写笔记&#xff0c;记录一下这难得的时光。 docker容器的出现 2013年初&#xff0c; 一个名字从云计算领域横空出世&#xff0c;并在整个IT行业激起千层浪&#xff0c;这就是Docker。Docker选择容器作为核心和基础&…

与六年测试工程师促膝长谈,他分享的这些让我对软件测试工作有了全新的认知~

不知不觉已经从事软件测试六年了&#xff0c;2016年毕业到进入外包公司外包给微软做软件测试&#xff0c; 到现在加入著名的外企。六年的时间过得真快。长期的测试工作也让我对软件测试有了比较深入的认识。但是我至今还是一个底层的测试人员&#xff0c;我的看法都比较狭隘&am…

知识点滴 - 电源适配器接头的极性

使用各种电子设备&#xff0c;肯定要使用到电源适配器(adapter)。在电源适配器的标签上面一般会有几项是需要注意的。第一&#xff0c;是adapter的“INPUT/输入”&#xff0c;在中国通用的一般是AC100-240V~50-60Hz&#xff0c;这说明这个adapter可以在100V-240V的交流电压下面…

自动驾驶过冬,需要点燃“降本增效”的炉火

进入2023年&#xff0c;人们还在等待这个冬天的第一场雪&#xff0c;同时也在感叹暖冬已经成为了新的常态。但对自动驾驶产业而言&#xff0c;这个冬天似乎并没有那么暖。回顾2022年&#xff0c;全球自动驾驶行业裁员、倒闭、部门裁撤的消息不绝于耳。资本市场也逐渐放弃了自20…

流浪气球?ChatGPT这样回答,我惊了

近日&#xff0c;流浪地球电影反响热烈&#xff0c;“流浪气球”事件讨论热火&#xff0c;连人工智能ChatGPT都发表了 “自己”的看法&#xff0c;到底是怎么一回事呢&#xff1f;起因是我国一只民用气球&#xff0c;因技术和天气原因不小心飘到了米国上空&#xff0c;对方当时…

vue组合式API及生命周期钩子函数

一、组合式API 什么是组合式API&#xff1f; vue3中支持vue2的选项式、支持新的编程模式–函数式编程&#xff08;没有this指针&#xff09;做了一个兼容&#xff0c;可以在一个组件中使用函数式编程和OOP编程&#xff08;选项式&#xff09; setup()函数 可以使用setup属性…

二叉树的遍历 (2023-02-11)

二叉树的遍历 二叉树的遍历分为&#xff1a;先序遍历、中序遍历、后序遍历和层次遍历。 1.先序遍历&#xff08;根左右&#xff09; &#xff08;1&#xff09;访问根节点 &#xff08;2&#xff09;左子树按根左右遍历 &#xff08;3&#xff09;右子树按根左右遍历 2.中序…

【Flume】高级组件之Channel Selectors及项目实践

文章目录1. 组件简介2. 项目实践2.1 Replicating Channel Selector实践2.1.1 需求2.1.2 配置2.1.3 运行2.2 Multiplexing Channel Selector实践2.2.1 需求2.2.2 配置2.2.3 运行1. 组件简介 通俗来讲&#xff0c;Channel Selectors组件控制Source采集到的数据分别流向哪些Channe…

Idea超好用的管理工具ToolBox(附带idea工具)

文章目录为什么要用ToolBox总结idea管理安装、更新、卸载寻找ide配置、根路径idea使用准备工作配置为什么要用ToolBox 快速轻松地更新,轻松管理您的 JetBrains 工具 安装自动更新同时更新插件和 IDE回滚和降级通过下载补丁或一组补丁而不是整个包&#xff0c;节省维护 IDE 的…

snakeyaml数字字符串显示单引号的问题

如题所示&#xff0c;一般yaml结构中&#xff0c;字符串直接显示没有单引号的字符串&#xff0c;如果字符串由数字组成&#xff0c;为了区别真正的数字&#xff0c;这个字符串会使用单引号包围起来。 数据结构如下&#xff1a; Map<String,Object> map new LinkedHashM…