1. 概述

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值。这两个问题是在实际应用中经常遇到的问题,下面介绍一下解决这两种问题的比较高效的算法。当然,该问题也可以用线段树(也叫区间树)解决,算法复杂度为:O(N)~O(logN),这里我们暂不介绍。

2.RMQ算法

对于该问题,最容易想到的解决方案是遍历,复杂度是O(n)。但当数据量非常大且查询很频繁时,该算法无法在有效的时间内查询出正解。

本节介绍了一种比较高效的在线算法(ST算法)解决这个问题。所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST(Sparse Table)算法是一个非常有名的在线处理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

F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。同理 F[1,1] = max(3,2) = 3, F[1,2]=max(3,2,4,5) = 5,F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

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

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

我们把F[i,j]平均分成两段(因为f[i,j]一定是偶数个数字),从 i 到i + 2 ^ (j - 1) - 1为一段,i + 2 ^ (j - 1)到i + 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])。

代码如下:

void rmq_isit(bool ok)
{
for(int i=;i<=n;i++)
mm[i][]=mi[i][]=a[i];
for(int j=;(<<j)<=n;j++)
{
for(int i=;i+(<<j)-<=n;i++)
{
if(ok)
mm[i][j]=max(mm[i][j-],mm[i+(<<(j-))][j-]);
else
mi[i][j]=min(mi[i][j-],mi[i+(<<(j-))][j-]);
} }
}

这里我们需要注意的是循环的顺序,我们发现外层是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])的最值,这里F[1,3] = max(max(A[0],A[1],A[2],A[3]),max(A[4],A[5],A[6],A[7]))的值,但是我们根本没有计算max(A[0],A[1],A[2],A[3])和max(A[4],A[5],A[6],A[7]),所以这样的方法肯定是错误的。

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

(二)然后是查询。

假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可以重复,比如查询5,6,7,8,9,我们可以查询5678和6789)。

因为这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。

举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2]);

在这里我们也需要注意一个地方,就是<<运算符和+-运算符的优先级。

比如这个表达式:5 - 1 << 2是多少?

答案是:4 * 2 * 2 = 16。所以我们要写成5 - (1 << 2)才是5-1 * 2 * 2 = 1。

int rmq(int l,int r)
{
int k=;
while((<<(k+))<=r-l+)
k++;
//printf("%d %d %d %d\n",l,l+(1<<k),r-(1<<k)+1,r-(1<<k)+1+(1<<k));
int ans1=max(mm[l][k],mm[r-(<<k)+][k]);
int ans2=min(mi[l][k],mi[r-(<<k)+][k]);
return ans1-ans2;
}

板子:poj 3264

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = ;
const int maxm = ;
int d_min[maxn][maxm],d_max[maxn][maxm],a[maxn];
int n; void RMQ_init()
{
int i,j;
for(i = ; i <= n; i++)
{
d_min[i][] = a[i];
d_max[i][] = a[i];
}
for(j = ; (<<j) <= n; j++)
for(i = ; i + j - <= n; i++)
{
d_min[i][j] = min(d_min[i][j-],d_min[i + (<<(j-))][j-]);
d_max[i][j] = max(d_max[i][j-],d_max[i + (<<(j-))][j-]);
}
} int RMQ_min(int l,int r)
{
int k = ;
while((<<(k+)) <= r-l+)
k++;
return min(d_min[l][k], d_min[r-(<<k)+][k]);
}
int RMQ_max(int l,int r)
{
int k = ;
while((<<(k+)) <= r-l+)
k++;
return max(d_max[l][k], d_max[r-(<<k)+][k]);
}
int main()
{
int q,l,r,i;
scanf("%d%d",&n,&q);
for(i = ; i <= n; i++)
scanf("%d",&a[i]);
RMQ_init(); while(q--)
{
scanf("%d%d",&l,&r);
printf("%d\n",RMQ_max(l,r)-RMQ_min(l,r));
}
return ;
}

RMQ求最值的更多相关文章

  1. RMQ区间最值查询

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

  2. ACM3 求最值

    /*2*2014.11.18*求最值*描述:给定N个整数(1<=N<=100),求出这N个数中的最大值,最小值.*输入:多组数据,第一行为一个整数N,第二行为N个不超过100的正整数,用空 ...

  3. [NOI2005]维修数列 Splay tree 区间反转,修改,求和,求最值

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1500 Description Input 输入文件的第1行包含两个数N和M,N表示初始时数 ...

  4. hdu4521-小明系列问题——小明序列(线段树区间求最值)

    题意:求最长上升序列的长度(LIS),但是要求相邻的两个数距离至少为d,数据范围较大,普通dp肯定TLE.线段树搞之就可以了,或者优化后的nlogn的dp. 代码为  线段树解法. #include ...

  5. javascript之求最值

    求最值: var selections = $("#deliveryGridSalesOrGoods").datagrid('getRows'); var costPrice = ...

  6. poj3264(线段树区间求最值)

    题目连接:http://poj.org/problem?id=3264 题意:给定Q(1<=Q<=200000)个数A1,A2,```,AQ,多次求任一区间Ai-Aj中最大数和最小数的差. ...

  7. Sql示例说明如何分组后求中间值--【叶子】

    原文:Sql示例说明如何分组后求中间值--[叶子] 这里所谓的分组后求中间值是个什么概念呢? 我举个例子来说明一下: 假设我们现在有下面这样一个表: type        name price -- ...

  8. 【RMQ】洛谷P3379 RMQ求LCA

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  9. RMQ求LCA

    题目链接 rmq求LCA,interesting. 一直没有学这玩意儿是因为CTSC的Day1T2,当时我打的树剖LCA 65分,gxb打的rmq LCA 45分... 不过rmq理论复杂度还是小一点 ...

随机推荐

  1. Delphi组件编辑器

    看到Dev中的cxGrid组件的编辑器很强大,于是很想探究一下,跟踪cxGrid的代码比较麻烦,但原理大概知道一二.首先来研究一下设计器双击cxGrid弹出一个编辑窗体,选择窗体中的一个内容后,属性编 ...

  2. npm scripts

    参考资料1:[https://docs.npmjs.com/misc/scripts] 参考资料2:[http://www.ruanyifeng.com/blog/2016/10/npm_script ...

  3. P5019 铺设道路

    #include<bits/stdc++.h> using namespace std; ]; ; int main() { cin>>n; ;i<=n;i++) cin ...

  4. WijmoJS 以声明方式添加 Vue 菜单项

    WijmoJS 以声明方式添加 Vue 菜单项 在V2019.0 Update2 的全新版本中,Vue框架下 WijmoJS 的前端UI组件功能得到再度增强. 如今,向wj菜单组件添加项的方法将不限于 ...

  5. php设计模式之注册模式

    注册模式,解决全局共享和交换对象.已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可.将对象注册到全局的树上.任何地方直接去访问. <?php class ...

  6. win10自带虚拟机的使用(Hyper-v)

    昨天刚发现的觉得特别好用,故推荐一下,跟VM虚拟机的使用方法是一样的 1.点击开始菜单中的<设置>图标,进入设置页码 2.点击<应用>图标,进入应用页码,并找到程序和功能 3. ...

  7. Swoft 2.0.5 更新,新增高效秒级定时任务、异常管理组件

    什么是 Swoft ? Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架.Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-F ...

  8. 移除django的旧版本

    移除django的旧版本 下面这一段代码打进去绝对能看到你想要的,根据这个路径去找版本文件夹,他的名字应该是django.2xx.xxx很长一段,请你删了它! import django import ...

  9. 操作系统(五)CPU调度

    CPU调度是多道程序操作系统的基础.

  10. Echarts ajax异步

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...