您的位置:首页 > 产品设计 > UI/UE

RMQ(Range Minimum Query)算法

2015-09-18 16:35 423 查看
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

主要方法及复杂度如下:

1、朴素(即搜索),O(n)-O(qn) online。

2、线段树,O(n)-O(qlogn) online。

3、ST(实质是动态规划),O(nlogn)-O(q) online。

ST算法(Sparse Table),以求最大值为例,设d[i,j]表示[i,i+2^j-1]这个区间内的最大值,那么在询问到[a,b]区间的最大值时答案就是max(d[a,k], d[b-2^k+1,k]),其中k是满足2^k<=b-a+1(即长度)的最大的k,即k=[ln(b-a+1)/ln(2)]。

d的求法可以用动态规划,d[i, j]=max(d[i, j-1],d[i+2^(j-1), j-1])。

4、RMQ标准算法:先规约成LCA(Lowest Common Ancestor),再规约成约束RMQ,O(n)-O(q) online。

首先根据原数列,建立笛卡尔树,从而将问题在线性时间内规约为LCA问题。LCA问题可以在线性时间内规约为约束RMQ,也就是数列中任意两个相邻的数的差都是+1或-1的RMQ问题。约束RMQ有O(n)-O(1)的在线解法,故整个算法的时间复杂度为O(n)-O(1)。
这里主要介绍一个ST算法。

用F[i,j]表示从第i个数开始连续2^j个数的最值!!!(注意不是到第2^j个数)

那么,可知每个区间有偶数个数(除了2^0),将这些区间分成两个区间,i到2^(j-1)-1,和i+2^(j-1)到2^j-1,所以可得动态方程为F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1]);

初始状态为F[i,0],即,从第i个数开始连续1个数的最值,也就是第i个数本身。

void RMQ(int num){
for(int j=1;j<32;j++){//int类型最大2^32-1
for(int i=1;i<=num;i++){
if(i+(1<<j)-1<=num){
maxsum[i][j]=max(maxsum[i][j-1],maxsum[i+(1<<(j-1))][j-1]);
minsum[i][j]=max(minsum[i][j-1],minsum[i+(1<<(j-1))][j-1]);
}
}
}
}


这里我们需要注意的是循环的顺序,我们发现外层是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]),所以这样的方法肯定是错误的。

预处理之后,开始query。
假设查询的区间是(i,j),那么int k=log2(i-j+1)。取最小幂。

举例说明,要求区间[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]);

例题(hdu 5443 The water problem):

/*
* mai.cpp
*
*  Created on: 2015年9月15日
*      Author: chen
*/

#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
#include<algorithm>
#include<vector>
#include<time.h>
#include<queue>
#include<stack>
#include<iterator>
#include<math.h>
#include<stdlib.h>
#include<limits.h>
#include<memory.h>
//#define ONLINE_JUDGE
#define eps 1e-8
#define INF 0x7fffffff
#define FOR(i,a) for((i)=0;i<(a);(i)++)                          //[i,a);
#define MEM(a) (memset((a),0,sizeof(a)))
#define sfs(a) scanf("%s",a)
#define sf(a) scanf("%d",&a)
#define sfI(a) scanf("%I64d",&a)
#define pf(a) printf("%d\n",a)
#define pfI(a) printf("%I64d\n",a)
#define pfs(a) printf("%s\n",a)
#define sfd(a,b) scanf("%d%d",&a,&b)
#define sft(a,b,c)scanf("%d%d%d",&a,&b,&c)
#define for1(i,a,b) for(int i=(a);i<b;i++)
#define for2(i,a,b) for(int i=(a);i<=b;i++)
#define for3(i,a,b)for(int i=(b);i>=a;i--)
#define MEM1(a) memset(a,0,sizeof(a))
#define MEM2(a) memset(a,-1,sizeof(a))
#define LL __int64
const double PI = acos(-1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
template<class T> inline T Min(T a, T b) { return a < b ? a : b; }
template<class T> inline T Max(T a, T b) { return a > b ? a : b; }
using namespace std;

template<class T>
T Mint(T a, T b, T c) {
if (a>b) {
if (c>b)
return b;
return c;
}
if (c>a)
return a;
return c;
}

template<class T>
T Maxt(T a, T b, T c) {
if (a>b) {
if (c>a)
return c;
return a;
}
else if (c > b)
return c;
return b;
}

#define maxn 50
int T,n,q,l,r;
int maxsum[1010][maxn];
int minsum[1010][maxn];

void RMQ(int num){
for(int j=1;j<20;j++){
for(int i=1;i<=num;i++){
if(i+(1<<j)-1<=num){
maxsum[i][j]=max(maxsum[i][j-1],maxsum[i+(1<<(j-1))][j-1]);
//minsum[i][j]=max(minsum[i][j-1],minsum[i+(1<<(j-1))][j-1]);
}
}
}
}

int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
while(~sf(T)){
while(T--){
sf(n);
for2(i,1,n){
sf(maxsum[i][0]);
}
RMQ(n);
sf(q);
while(q--){
sfd(l,r);
int k=(int)(log(r-l+1.0)/log(2.0));
int ans = max(maxsum[l][k], maxsum[r - (1<<k) + 1][k]);
pf(ans);
}
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: