RMQ算法(Range Minimum/Maximum Query)

如果让你一串数组,让你查询在一个区间[ L, R ] 的最大值或者最小值,很容易想到遍历一下,时间复杂度是O( n ),但是如果你查询m次呢(m 约1000000次作用),还能用遍历的方法么,显然是不能,所以就可以用到RMQ算法,时间复杂度是O( n * log n),还是比直接遍历节约时间。RMQ算法又叫做在线查找,为什么叫在线查找,因为经过预处理,所有的范围最大值,或者最小的值都已经找到了,直接查找就OK了,查找的时间复杂度是O(1)。

1.神马是RMQ算法?

      RMQ算法是不是听起来非常的高大尚,其实用起来也非常的略屌。下面是RMQ的科学定义:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

 2.RMQ顺发是什么思想?

    RMQ是根据动态规划的思想,F[ i, j] = min (F[ i, j - 1], F [i + (1<<(j - 1)), j-1])。不断地更新达到想要的数据, F[ i, j]用i来储存比较其距离i的位置1<<j (也就是2^j)个单位的距离的最大值,这就是动态规划的思想。

 3.RMQ算法如何的预处理?

        先上代码:

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

这串代码看不懂很正常,接下来我来举个例子:3  5  1  10  12

用一个数组F[ i, j ]来不断更新结果,首先是F [ i, 0 ] = a[i], 这是原始赋值,开始第一次遍历 F[ 1,1] = min (F[ 1, 0], F [1 + 1, 0]= 5, F[ 2,1] = min (F[ 2, 0], F [2 + 1, 0]) = 5, F[ 3,1] = min (F[ 3, 0], F [3 + 1, 0]) = 10, F[ 4,1] = min (F[ 4, 0], F [4 + 1, 0]) = 12. 第二次遍历,F[ 1,2] = min (F[ 1, 1], F [3, 1]) = 5, F[ 2,2] = min (F[ 2, 1], F [4, 1]) = 12。然后结束了,其实就是先俩来比较,让后用左边的去存储两个数中最大的,然后用四个比较从中间分成两份,因为只需比较跟他差一个位置的就行,因为例题数据比较小,所以也就更新到四个数之间的比较就结束了,如果数据比较多的话,还可以接着更新,八个比较,分成两份,只需比较与他差三个位置的数进行比较就可以了。

4.如何查找?

因为预处理之后,很多范围的最大值或者最小值都应经更新完了,但是这些跟新的只是距离他左侧范围L的位置距离是2^n,但是很多时候查询不可能就是你更新的范围,所以就有一种覆盖的思想,虽然有的地方会会重复但是没有影响。计算公式:k = log2(r - l + 1), min = min( F[ l, k], F [ r - (1<<k) + 1, k]),就比如查询[ 3, 8 ]这个区间的最小值,这个范围的数包含6个数,所以这么查询k = log2(8 - 3) = 2(向下取整),F [3 , 2] 是从三开始的4个数中最大的,也就是从3 到 6中的最大值 ,而F [5, 2 ]也就是5到8的最大值,5, 6这两个数已经被覆盖了,所谓的覆盖其实就是多比较了一遍,但是没有啥影响。

5.例题

下面的例题使纯练习的题,没有任何的弯路,就是这就测试,你敲的代码是不是正确的。

洛谷   P1816 忠诚 

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

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<iostream>
using namespace std;
#define N 100010
int a[N], mn[N][100], b[N];
int val;
void rmq(int m){
     for(int i = 1; i <= m; i++)
        mn[i][0] = a[i];
     for(int j = 1; (1 << j) <= m; j++){
        for(int i = 1; (i + (1<<j) - 1) <= m; i++){
            mn[i][j] = min(mn[i][j-1], mn[i+(1<<(j-1))][j-1]);
        }
     }
}
void Query(int l, int r){
    int k = (int)(log(r - l + 1)/ log(2));
    val = min(mn[l][k], mn[r-(1<<k)+1][k]);
}
int main(){
    int n, i, m, l, r;
    cin>>n>>m;
    int cn = 0;
    for(int i = 1; i <= n; i++){
        cin>>a[i];
    }
    rmq(n);
    while(m--){
        cin>>l>>r;
        Query(l, r);
        b[cn] = val;
        cn++;
    }
    for(int i = 0; i < cn; i++)cout<<b[i]<<" ";
    cout<<endl;
return 0;
}

Poj   Balanced Lineup

链接:http://poj.org/problem?id=3264

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define N 50010
int ma[N][100], mn[N][100];
int a[N];
int RMQ(int n){
    int i, j;
    for(i = 1; i <= n; i++){
        mn[i][0] = ma[i][0] = a[i];
    }
    for(j = 1; (1<<j) < n; j++){
        for(i = 1; i +(1<<j) - 1<= n; i++){
            ma[i][j] = max(ma[i][j-1], ma[i+(1<<(j-1))][j-1]);
            mn[i][j] = min(mn[i][j-1], mn[i+(1<<(j-1))][j-1]);
        }
    }
}
int QueryMax(int l, int r){
    int k = log(r-l+1)/log(2);
    int f = max(ma[l][k], ma[r-(1<<k)+1][k]);
    return f;
}
int QueryMin(int l, int r){
    int k = log(r-l+1)/log(2);
    int f= min(mn[l][k], mn[r-(1<<k)+1][k]);
    return f;
}
int main(){
    int n, m, l, r;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
    }
    RMQ(n);
    while(m--){
        scanf("%d %d", &l, &r);
        printf("%d\n", QueryMax(l, r)- QueryMin(l, r));
    }
return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值