RMQ(Range Minimum Query)算法

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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值