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,因此有时需要离散化让空间压缩。
具体参考博客:线段树

例题

以下的例题都可以分别用ST算法和线段树算法来做,但是代码几乎是一样的,有些就省略了

数列区间最大值

题目链接: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;
}

线段树算法
模板题,跟天才的记忆是几乎一样的代码

最敏捷的机器人

题目链接: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算法
跟上面两个题代码一样
线段树算法
线段树算法详情参考这篇博客,并且这个代码量会偏大一些,实际这个区间数组是不变的,用线段树有点大材小用了:
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;
}

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 41  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)

RMQ--区间最值问题相关推荐

  1. RMQ问题-ST表倍增处理静态区间最值

    简介 ST表是利用倍增思想处理RMQ问题(区间最值问题)的一种工具. 它能够做到O(nlogn)预处理,O(1)查询的时间复杂度,效率相当不错. 算法 1.预处理 ST表利用倍增的思想.以洛谷的P38 ...

  2. RMQ算法,求区间最值

    poj 3264 Balanced Lineup@ 2016-07-27 11:15 49人阅读 评论(0) 收藏 举报  分类: RMQ(Range MinimumMaximum Quer)(4)  ...

  3. RMQ(求区间最值问题)

    学习博客:https://blog.csdn.net/qq_31759205/article/details/75008659 RMQ(Range Minimum/Maximum Query),即区间 ...

  4. ST算法 - RMQ(区间最值问题)—— 倍增

    文章目录 引入倍增: 例题1:区间和 例题2:Genius ACM 应用: ST算法 求解 RMQ(区间最值问题) 模板Code: 练习题: ST算法 维护区间最大公约数 例题:Pair of Num ...

  5. RMQ的ST算法(区间最值)

    ST算法求解RMQ问题(区间最值) 效率:O(n log n)预处理,O(1)询问 思想: 用 f [ i ][ j ] 表示 以i 开头的区间,包括2^j 个元素的一段区间的最值 那么有初始化的初始 ...

  6. 牛客网【每日一题】7月21日题目精讲—区间权值

    来源:牛客网: 区间权值 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 1048576K,其他语言2097152K 64bit IO Format: %lld 题目描述 输入描述: ...

  7. 问题 G: 区间权值

    问题 G: 区间权值 时间限制: 1 Sec  内存限制: 128 MB 提交: 112  解决: 49 [提交] [状态] [讨论版] [命题人:admin] 题目描述 小Bo有n个正整数a1..a ...

  8. HihoCoder - 1483 区间最值

    给定n个数A1...An,小Ho想了解AL..AR中有多少对元素值相同.小Ho把这个数目定义为区间[L,R]的价值,用v[L,R]表示. 例如1 1 1 2 2这五个数所组成的区间的价值为4. 现在小 ...

  9. Segment Tree Beats 区间最值问题

    Segment Tree Beats 区间最值问题 线段树一类特殊技巧! 引出:CF671C Ultimate Weirdness of an Array 其实是考试题,改题的时候并不会区间取最值,区 ...

  10. hdu1754 I hate it线段树模板 区间最值查询

    题目链接:这道题是线段树,树状数组最基础的问题 两种分类方式:按照更新对象和查询对象 单点更新,区间查询; 区间更新,单点查询; 按照整体维护的对象: 维护前缀和; 维护区间最值. 线段树模板代码 # ...

最新文章

  1. python使用imbalanced-learn的RepeatedEditedNearestNeighbours方法进行下采样处理数据不平衡问题
  2. c语言调用shell脚本或命令
  3. android studio tree,Git 、Sourse Tree 和 Android Studio配置遇到的问题
  4. 美摄 - 助力打造完善的音视频解决方案
  5. 厚积薄发,拥抱 .NET 2016
  6. 菜鸟学Java(六)——简单验证码生成(Java版)
  7. 如何使用消息队列,Spring Boot和Kubernetes扩展微服务
  8. Python实战之Selenium自动化测试web刷新FW
  9. UiPath: Studio 快捷键
  10. 阅读《经济学人》,学会这样查词典,从此英语学习不求人
  11. 访问 github.com 的请求遭到拒绝您未获授权,无法查看此网页解决办法
  12. linux 连接打印机
  13. jumpserver
  14. 深度学习的发展历史是什么?
  15. 移动通信的主要测量指标及注意事项(转)
  16. 天池比赛——docker初步尝试
  17. 无线蓝牙手表FCC ID认证测试项目有哪些?
  18. android老人字体变大,适合老年人用的安卓手机软件 一键让Android字体变大
  19. 读取工程下的文档 统计重复的姓名 并按次数排序 java_java并打印出重复的姓名和重复的次数,并按重复次数排序...
  20. qsort 函数的使用

热门文章

  1. python中飞机票购买程序_「最低折扣机票查询」Python 爬取携程所有机票找出最低折扣机票,让你无忧回家过年 - seo实验室...
  2. Python3数据分析与挖掘建模(6)单因子分析:离散分布分析示例
  3. 单细胞转录组测序技术(scRNA-seq)及细胞分离技术分类汇总
  4. 2022年全球与中国红外探测器芯片市场现状及未来发展趋势
  5. 申宝剖析沪深两市股指高开高走
  6. Spring Cloud Alibaba搭建(二):Nacos注册中心
  7. 什么是中间件?中间件的作用,怎么使用中间件及应用场景
  8. oracle支付预付款时账务处理,支付设备预付款的账务处理
  9. GPS 校验和 代码_今日份∣学习(三菱-菱云系列)电梯故障代码表
  10. 随机生成5位数验证码