浅谈RMQ算法

前言

这篇文章,是一篇总结RMQ算法的一篇文章,若有疑问或建议,请于下方留言,谢谢!

RMQ是什么?

RMQ(Range Minimum/Maximum Query),即区间最值查询。

是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值。

这两个问题是在实际应用中经常遇到的问题,而RMQ算法是解决这两种问题的比较高效的算法。

当然,该问题也可以用线段树解决,算法复杂度为:O(N)~O(logN),而线段树代码远远比我们今天讨论的RMQ要长要难理解的多,这里我们暂不介绍。

我们遇到这种区间最值查询问题的时候,通常会想到两种想法

每次查询时暴力扫描区间,时间复杂度单次查询O(n),n次查询便是O(n^2),超过1e4基本上就TLE了
开一个二维数组(空间复杂度O(n*n)),预处理答案在数组中,预处理复杂度O(n^2),单次查询O(1)。
显然这些算法都是渣渣,慢的要死,那么有什么搞基高级的方法来解决这一类问题呢?

答案是倍增思想。(大概是这样的)

RMQ算法描述

我们以最大值来诠释RMQ算法

  1. 令A[i]存储我们要求的数列,F[i][j]表示从第i个位置开始,往后的2^j-1个数字中,最大的数字是多少。
  2. 预处理F数组,对j从大到小枚举。每次处理F[i][j]的时候,F[i][0~j-1]都已经处理好了,那么F[i][j]=max(F[i][j-1],F[i+2^(j-1)][j-1]),因为j最大到log2n,所以预处理的操作的时间复杂度为O(nlog2(n))。
  3. 有了F数组啦,现在我们查询的话就很简单了。比如说我们查询区间(i,j),那么我们令k =⌊log2(i)+j-1⌋,那么我们查询的结果就是max(F[i][j],F[j-(2^k)+1][k]),可以明显看到时间复杂度为O(1)的,而且保证能够覆盖到整个区间。

代码描述如下

预处理操作

void init_rmq(int m) {
    for(int i=1;i<=m;i++) 
        f[i][0]=a[i];
    for(int j=1;(1<<j)<m;j++) {
        for(int i=1;i<=m;i++) {
            if(i+(1<<j)-1 <= m) {
                f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
            }
        }
    }
}

查询操作

int query_rmq(int l,int r) {
    int i=0;
    for(;(1<<i)+l-1<=r;i++){};
    i--;
    return min(f[l][i],f[r-(1<<i)+1][i]);
}

RMQ算法的不足

我们发现RMQ算法复杂度竟然如此低,能够在O(log2(n))的时间内预处理,O(1)的复杂度单点查询,经常用于优化常数(卡常数是个神奇的东西),看来是一个好东西,但是但是

它不支持修改,只能维护最大最小值,不能维护区间和等会影响答案的东西(此时还是打那骚长的线段树吧)

RMQ算法例题

这里给出习题练练手吧

洛谷P1816 忠诚 (传送门

题目描述

老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。

输入输出格式

输入格式:

输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。

第二行为m个数,分别是账目的钱数

后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。

输出格式:

输出文件中为每个问题的答案。具体查看样例。

输入输出样例

输入样例#1:

10 3
1 2 3 4 5 6 7 8 9 10
2 7
3 9
1 10
输出样例#1:

2 3 1
解决方案

这题目实在是不能再不能裸的RMQ裸题了,如果没有完全掌握的RMQ的话还是可以练练手的,或者可以掌握思想后来实现一番,颇有成就感,比线段树好看多了。。。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const int size =1e5+10;
int a[size];
int f[size][40];

void init_rmq(int m) {
    for(int i=1;i<=m;i++) 
        f[i][0]=a[i];
    for(int j=1;(1<<j)<m;j++) {
        for(int i=1;i<=m;i++) {
            if(i+(1<<j)-1 <= m) {
                f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
            }
        }
    }
}

int query_rmq(int l,int r) {
    int i=0;
    for(;(1<<i)+l-1<=r;i++){};
    i--;
    return min(f[l][i],f[r-(1<<i)+1][i]);
}

int main() {
    int m,n;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++) 
        scanf("%d",&a[i]);
    init_rmq(m);
    for(int i=1,l,r;i<=n;i++) {
        scanf("%d%d",&l,&r);
        printf("%d ",query_rmq(l,r));
    }
    return 0;
}
发布了39 篇原创文章 · 获赞 7 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章