如果让你一串数组,让你查询在一个区间[ 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;
}