RMQ(Range Minimum/Maximum Query),即区间最值查询。RMQ算法一般用较长时间做预处理,时间复杂度为O(nlogn),然后可以在O(1)的时间内处理每次查询。RMQ算法讲解
我们设二维数组dp[i][j]表示从第i位开始连续2j个数中的最小值。
我们假设数组arr为:1,2,6,8,4,3,7
那么dp[2][1]就表示从第二位数开始连续两个数的最小值(也就是从第二位数到第三位数的最小值),即2,6中的最小值,所以dp[2][1] = 2;另外dp[i][0]就表示第i个数字本身
RMQ的思想:(倍增)
其实感觉RMQ有点像区间dp。它每次由两个长度为1的区间得到一个长度为2的区间的最值、再由两个长度为2的区间得到一个长度为4的区间的最值、再由两个长度为4的区间得到一个长度为8的区间的最值……这就是预处理的部分,处理时枚举所有区间长度和区间的起点,时间复杂度为O(nlogn)。
转移方程:dp[i][j] = min(dp [i][j - 1], dp [i + (1 <<( j - 1))][j - 1])
然后RMQ的思想也就决定了它代码中两层循环的先后顺序:它需要先将所有短的区间先处理完,再处理长的区间
//预处理
for(int i=1; i<=n; i++)
{
scanf("%d",&h);
dp_max[i][0]=h;//初始化
}
for(int j=1; j<=20; j++)
for(int i=1; i+((1<<j)-1)<=n; i++)
dp_max[i][j]=max(dp_max[i][j-1],dp_max[i+(1<<(j-1))][j-1]);
//维护[i,i+2^(j-1)-1] 维护[i+2^(j-1),i+2^j-1]
RMQ的查询部分:
它的查询实际上也是由两个区间得来的,而且因为区间最值的特殊性,查询的这两个区间允许有重叠的部分,比如下图中的这个大区间[l, r]的最大值就等于图中两个小区间的最大值再取一个最大值。
在查询区间[l, r]的过程中,我们把l作为一个区间的左端点,r作为另一个区间的右端点,然后如果我们能够保证两个区间的长度大于或等于区间[l, r]长度的一半,就能够得到区间[l, r]的最值
这里我们取k=log2(r-l+1),2^k为两个小区间的长度。比如查询长度为10的区间,就取k=3,2^k=8;查询长度为16的区间,就取k=4,2^k=16;查询长度为31的区间,就取k=4,2^k=16。(可以看出2^k永远大于r-l+1/2)
//查询
int query(int l,int r)
{
int k=log2(r-l+1);
return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
ps:从RMQ的预处理部分中可以知道,我们处理的区间的长度都是2的幂,虽然没有处理完所有的区间,但是最后我们可以通过两个允许重叠的区间得到所查询区间解,比如max[5,10]=max(max[5,8],max[7,10])。但如果要维护区间和之类的话,RMQ就不适用了。
例题:http://poj.org/problem?id=3264
代码:
#include <iostream>
#include <iostream>
#include <string.h>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
typedef long long LL;
using namespace std;
const int manx=1e5+10;
const int INF=0x3f3f3f3f;
int n,q,h,dp_min[manx][50],dp_max[manx][50],k,s,e;
int main()
{
while(scanf("%d%d",&n,&q)!=EOF)
{
for(int i=1; i<=n; i++)
{
scanf("%d",&h);
dp_min[i][0]=h;
dp_max[i][0]=h;
}
for(int j=1; j<=20; j++)
for(int i=1; i+((1<<j)-1)<=n; i++)
{
dp_max[i][j]=max(dp_max[i][j-1],dp_max[i+(1<<(j-1))][j-1]);
dp_min[i][j]=min(dp_min[i][j-1],dp_min[i+(1<<(j-1))][j-1]);
}
for(int i=1; i<=q; i++)
{
scanf("%d%d",&s,&e);
k=log2(e-s+1);
int mmin=min(dp_min[s][k],dp_min[e-(1<<k)+1][k]);
int mmax=max(dp_max[s][k],dp_max[e-(1<<k)+1][k]);
printf("%d\n",mmax-mmin);
}
}
}
附:
log2()用子函数预处理的写法:
//math.h中log2()复杂度是O(log n)
//直接搬学长代码了,嘿嘿
int LOG[maxN];
void pre()
{
for(int i = 1, j = 0, nex = 2; i<maxN; i++)
{
if(i == nex)
{
nex <<= 1;
j++;
}
LOG[i] = j;
}
}