RMQ即Range Minimum/Maximun Query,中文意思:查询一个区间的最小值/最大值

比如有这样一个数组:A{3 2 4 5 6 8 1 2 9 7},然后问你若干问题:

数组A下标2~7区间最小的值是多少?       最小值是(1)

数组A下标3~6区间最小的值是多少?       最小值是(4)

数组A下标1~10区间最小的值是多少?      最小值是(1)

......

专业术语:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值。

这个问题其实可以用线段树轻松实现O(N)预处理,O(logN)查询,已经是很不错的复杂度了。

但是:有的时候查询操作很多,所以我们需要一个O(1)的查询算法。

那就是Sparse Table 即ST算法

ST算法是比较高效的在线算法。所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

 

(一)首先是预处理,用动态规划(DP)解决。

设A[i]是要求区间最值的数列,F[i, j]表示从从i开始的连续2^j个数中的最大值。(DP的状态)

例如:A数列为:3  2  4  5  6  8  1  2  9   7   12   3   21

下标为:1  2  3  4  5  6  7  8  9  10  11  12  13

F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是A数列中3这个数。同理:

F[1, 1] = max(3,2) = 3,

F[1,2]=max(3,2,4,5) = 5,

F[3,2]=max(4,5,6,8) = 8, F[3,2]表示从第三个数4开始连续(2^2)4个数中的最大值8

F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

并且我们可以容易的看出F[i,0]就等于A[i]。(DP的初始值)

假如i=3,那么F[i, 0]=max(4,4)=4,  A[3]=4, 所以DP的初值就是F[i,0]=A[i]

 

这样,DP的状态、初值都已经有了,剩下的就是状态转移方程。 

我们把F[i,j]平均分成两段(因为F[i,j]一定是偶数个数字),

前半段为 i 到i + 2 ^ (j - 1) - 1,     (请动手计算一下)

后半段为i + 2 ^ (j - 1)到i + 2 ^ j - 1  需要注意的是2 ^ (j - 1)2 ^ j - 1 的区别

前后段的长度都为2 ^ (j - 1)。

用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。

F[i,j]就是这两段各自最大值中的最大值。

于是我们得到了状态转移方程: 

f[i,j]=max(f[i,j-1],f[i+2^(j-1),j-1]);

方程前半段为f[i,j-1] 表示 从i 起连续2^(j-1)个数的最大值。

方程后半段f[i+2^(j-1),j-1] 表示i + 2 ^ (j - 1)起连续2^(j-1)个数据的最大值。

然后取前后两段各自最大值中的最大值。

总结:求从i起连续2^j个数的最大值,就是把2^j 分成前后两个2^(j-1),分别取其最大值,再通过比较获得此状态最大值。

 

我们先来学习个小知识点“<<”位移符号的使用

C或C++中:

<<可作为左移运算符 (向左移一位,右边自动补0)

比如:i<<4,是按位左移4位。

比如8<<4:

二进制状态下:

0000 0000 0000 1000 =  十进制8

                 ↓

0000 0000 1000 0000 =  十进制128   128=8^4

十进制状态下:

1<<1 等价于 1*2^1

1<<2 等价于 1*2^2

3<<3 等价于 3*2^3

5<<4 等价于 5*2^4

听说位移"<<" 速度比乘法快。

注意+-运算符优先于<<符号

比如:5-1<<1等同(5-1)*2^1 ,而不是5-1*2^1

所以如果要表示 5-1*2^1,要写成5-(1<<1)

DP预处理的代码如下:请选择一种适合自己的方法作为模板。 

//第一种写法:请理解好循环条件的意义

int n;  //n是数组元素个数
int a[100004]; //数列
int f[100004][30]; //f[i][j]
//f[i][j]表示以i为起点,区间长度为2^j的一段区间的最小(大)值
void RMQ_ST (  )   ////预处理ST表,数组中共n个元素  O(nlogn)  
{  
for (int i=1;i<=n;i++)   f[i][0]=a[i];//dp初始值
    for(int j=1;j<=20;j++)  //为什么是20? 2^j最大可以是多大?
        for ( int i = 1; i <= n; i++ )  //思考为什么外循环j套i,而不是外循环i套j
            if(i+(1<<j)-1<=n)  //“<<”符号请看前面知识点 (1<<j)注意括号
            {  
              int s=i+(1<<(j-1)); //后半段的起点  等价 i+2^(j-1)
              f [i][j]=max ( f [i][j-1], f [s][j-1] );  //区间最大值,最小值用min函数
            }  
}

//第二种写法:请理解好循环条件的意义

void RMQ_ST2 ( )   //预处理ST表,数组中共n个元素  O(nlogn)
{
for (int i=1;i<=n;i++)   f[i][0]=a[i];  //dp初始值
for (int j=1;(1<<j)<=n; j++)         //注意(1<<j)加上括号
   for (int i=1;i+(1<<j)-1<=n;i++)  //
      f [i][j]=max( f [i] [j-1], f [i+(1<<(j-1))] [j-1] );//求最小值是函数min
 }

 

这里我们需要注意的是循环的顺序,我们发现外层是j,内层所i,这是为什么呢?

可以是i在外,j在内吗?

答案是不可以。因为我们需要理解这个状态转移方程的意义。

状态转移方程的含义是:我们先得到F[i,0](即1个元素)的值,然后通过2个1个元素的最值,获得所有长度为F[i,1](即2个元素的最值),然后再通过2个2个元素的最值,获得所有长度为F[i,2](即4个元素的最值),以此类推更新所有长度的最值。

而如果是i在外,j在内的话,我们更新的顺序就是F [1,0],F [1,1],F [1,2],F [1,3],表示更新从1开始的1个元素,2个元素,4个元素,8个元素(a[0],a[1],....a[7])的最值,这跟我们的思维和计算方法完全不同,所以这样的方法肯定是错误的。

为了避免这样的错误,一定要好好理解这个状态转移方程所代表的含义。

(二)接着解决怎么样查询。 

 

先看小知识:

幂:指乘方运算的结果。nm指将n自乘m次(针对m为正整数的场合)。把nm看作乘方的结果,叫做“n的m次幂”或“n的m次方”。

其中,n称为“底数”,m称为“指数”(写成上标)。当不能用上标时,通常写成n^m。

对数:如果 ,即a的x次方等于N(a>0,且a≠1),那么x叫做以a为底N的对数,记作。其中,a叫做对数的底数,N叫做真数

特别注意:如果在c或c++语言里,注意加上头文件cmath或math.h。

对数形式应该写成x=loga (N),其中a是底数,(N)是真数,真数N一定要用()。

比如x=log2 (8),那么x=3。注意:log和2之间不要有空格。

还可以写成另外一种形式:x=log(N) / log(a),真数和底数都要加括号,而且真数在前面,不建议用这个形式。

查询:

假设要查询从(i, j)这一段的最大值, 这个区间的长度我们可以计算得出是j - i + 1

那么我们先求出一个最大的k, 使得k满足2^k <= (j - i + 1).

我们可以取k=log2( j - i + 1),举例说明:

要求区间[1,5]的最大值,k =(int)(log2(5 - 1 + 1))= 2。(计算机语言写法),

(int)是用来向下取整。(想想为什么要向下取整)

我们就可以把[i, j]分成两个(部分重叠的)长度为2^k的区间: [i, i+2^k-1], [j-2^k+1, j];

比如查询数列A:5,6,7,8,9,我们可以查询(1, 4) 和(2, 5)这两个区间。

就是查询5 6 7 86 7 8 9 ,原数列左边界i和右边界j是不变的, 只是中间的三个数6 7 8重叠。

又比如数列B: 4 5 6 7 8 9   经计算数列B长度为 j-i+1=6,那么:

k=int(log2(j-i+1))=2  (int)是用来向下取整。(想想为什么要向下取整)

[i, i+2^k-1]区间计算后为[1,4]  包含元素为 4 5 6 7共4个。

[j-2^k+1, j]区间计算后为[3,6]  包含元素为 6 7 8 9 也是4个。

而我们之前dp预处理时已经求出了:

f(i, k)为区间[i, i+2^k-1]的最大值, 比如上面的f(1, 4)我们在预处理时已经求出来了。

f(j-2^k+1, k)为区间[j-2^k+1, j]的最大值,比如上面的f(2, 5)我们在预处理时也已经求出来了。

我们只要返回其中更大的那个, 就是我们想要的答案, 这个算法的时间复杂度是O(1)的.

则有:RMQ(A, i, j)=max{ f [i , k], f [ j - 2 ^ k + 1, k]}。(伪代码)

又比如:要求区间[2,8]的最大值,k =(int)( log2(8 - 2 + 1))= 2

即求max (f [2, 2],f [8 - 2 ^ 2 + 1, 2]) = max (f [2, 2],f[5, 2]);

而f [2, 2]和f[5, 2]在前面已经预处理过了

请计算一下区间[1 ,11]的最大值,像上面一样手写过程。

查询代码如下:

int rmq_ask (int l, int r) //查询区间(l, r)
{
int k=(int) (log2(r-l+1));  
//l->r区间长度---为2^k(向下取整) 千万记住对数一定要整体括起来
    return max (f[l][k], f[r-(1<<k)+1][k]); //根据需要取min或者max
}

ST算法解RMQ模板(洛谷1816 忠诚)

https://www.luogu.org/problemnew/show/P1816

P3865 【模板】ST表

https://www.luogu.org/problemnew/show/P3865

P3379 【模板】最近公共祖先(LCA)请用RMQ

https://www.luogu.org/problemnew/show/P3379

参考文章:

http://blog.csdn.net/qq_35776409/article/details/62890728

http://blog.csdn.net/u012860063/article/details/40752197

http://blog.csdn.net/qq1169091731/article/details/51981497

https://wenku.baidu.com/view/6a7d691aa8114431b90dd877.html

ST表 求 RMQ(区间最值)的更多相关文章

  1. ST表(查询区间最值问题)

    ST表与线段树相比,这是静态的,无法改动,但是他的查询速度比线段树要快,这是牺牲空间换时间的算法. O(nlogn)预处理,O(1)查询.空间O(nlogn). ][]; ]; void rmq_in ...

  2. [51nod 1766]树上的最远点对 (树的直径+ST表求lca+线段树)

    [51nod 1766]树上的最远点对 (树的直径+ST表求lca+线段树) 题面 给出一棵N个点的树,Q次询问一点编号在区间[l1,r1]内,另一点编号在区间[l2,r2]内的所有点对距离最大值.\ ...

  3. RMQ区间最值查询

    RMQ区间最值查询 概述 RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A, 回答若干询问RMQ(A,i,j)(i,j<= ...

  4. st表求区间最大值

    Input 第一行给出一个数字N,接下来N+1行,每行给出一个数字Ai,(0<=i<=N<=1E6)接来给出一个数字Q(Q<=7000),代表有Q个询问每组询问格式为a,b即询 ...

  5. RMQ(区间最值问题)

    问题: RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大) ...

  6. ST表解决RMQ问题

    RMQ问题: RMQ(Range Minimum/Maximum Query),区间最值查询.对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间 ...

  7. POJ 3264 Balanced Lineup 【ST表 静态RMQ】

    传送门:http://poj.org/problem?id=3264 Balanced Lineup Time Limit: 5000MS   Memory Limit: 65536K Total S ...

  8. ST表(离线RMQ)

    离线RAQ时,预处理为O(n*lgn),查询为O(1)的算法,比较有意思的一种算法 放个模板在这可以随时看 //ST表(离线) //预处理 O(n*lgn) , 查询 O(1) #include &l ...

  9. 基于ST表的RMQ

    RMQ算法,是一个快速求区间最值的离线算法,预处理时间复杂度O(n*log(n))查询O(1),所以是一个很快速的算法,当然这个问题用线段树同样能够解决. 问题:给出n个数ai,让你快速查询某个区间的 ...

随机推荐

  1. 动态规划精讲(一)LC 最长递增子序列的个数

    最长递增子序列的个数 给定一个未排序的整数数组,找到最长递增子序列的个数. 示例 1: 输入: [1,3,5,4,7]输出: 2解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, ...

  2. nuxt打包等注意事项

    打包步骤: 1.首先执行 npm run build 2.将打包好的 .nuxt static nuxt.config.js package.json 这四个文件丢到服务器的某个文件夹中,在服务器上安 ...

  3. Shell系列(17)- 配置文件功能(待完善)

    配置文件功能 文件名 功能 相关联命令 /etc/profile USER变量 LOGNAME变量 MAIL变量 PATH变量 HOSTNAME变量 umask 调用/etc/profile.d/*. ...

  4. python学习笔记(十六)-Python多线程多进程

    一.线程&进程 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程, ...

  5. 『Python』matplotlib的imshow用法

    热力图是一种数据的图形化表示,具体而言,就是将二维数组中的元素用颜色表示.热力图之所以非常有用,是因为它能够从整体视角上展示数据,更确切的说是数值型数据. 使用imshow()函数可以非常容易地制作热 ...

  6. 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 百篇博客分析OpenHarmony源码 | v36.04

    百篇博客系列篇.本篇为: v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CP ...

  7. Python isinstance() 函数 Python 内置函数 Python 内置函数

    描述 isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type(). isinstance() 与 type() 区别: type() 不会认为子类是一种父类类型,不考虑继承关 ...

  8. iOS 15 无法弹出授权弹框之解决方案---Your app uses the AppTrackingTransparency framework, but we are unable to locate the App Tracking Transparency permission request when reviewed on iOS 15.0

    2021年9月30日下午:我正愉快的期盼着即将到来的国庆假期,时不时刷新下appstoreconnect的网址,28号就提上去的包,今天还在审核中....由于这个版本刚升级的xcode系统和新出的iO ...

  9. HTML选择器的四种使用方法

    选择器<style> 为了让.html代码更加简洁,这里引入选择器style 本文总共介绍选择器的四种使用方式 一.选择器的四种形式 1.ID选择器 id表示身份,在页面元素中的id不允许 ...

  10. t-SNE 从入门到放弃

    t-SNE 算法 1 前言 t-SNE 即 t-distributed stochastic neighbor embedding 是一种用于降维的机器学习算法,在 2008 年由 Laurens v ...