RMQ算法分析-区间最最值查询

RMQ(range minV/maxV query)算法是一个快速求区间最值得离线算法,预处理时间为O(nlgn),查询时间为O(1)。这个问题也可以用线段树解决。


问题描述:给出n个数ary[i],快速求出某一区间的最值。


算法思想:DP + 位运算


算法分析,状态:dp[i][j] 表示第i为开始,到(i + 2^j - 1)位的最大/小值。

状态转移方程建立:dp[i][j]可以分为两部分,(i, i + 2^(j-1) - 1), (i + 2^(j-1), i + 2^j - 1)

其实就是把(i, i + 2 ^j)这个区间分为了两部分,没一部分长度为2^(j - 1)。

状态转移方程:dp[i][j] = max(dp[i][j-1], dp[i+(1<<(j-1))][j-1]。(这是求最大值的,若求最小值,把max()换为min()即可)

//需要一个一维待查询的数组,及其长度n,两个状态数组。 
void RMQ() {  
    for (int i = 1; i <= n; ++i) {  
        dpMax[i][0] = dpMin[i][0] = ary[i];  
    } 
    for (int j = 1; (1 << j) <= n; ++j) {  
        for (int i = 1; i + (1 << j) - 1 <= n; ++i) {  
            dpMax[i][j] = max(dpMax[i][j - 1], dpMax[i + (1 << (j - 1))][j - 1]);  
            dpMin[i][j] = min(dpMin[i][j - 1], dpMin[i + (1 << (j - 1))][j - 1]);  
        }  
    }  
} 
那么查询的时候对于任意一个区间 L -- R ,我们同样可以得到区间差值 len = (R - L + 1)。

那么我们这一用小于2^k<=len,的 k 把区间分成可以交叉的两部分L 到 L+2^(k)- 1, 到 R -(1<


查询代码:

int queryRMQ(int L, int R) {  
    int k = 0, len = R - L + 1;  
    while ((1 << (k + 1)) <= len)  
        k++;    
    int maxV = max(dpMax[L][k], dpMax[R - (1 << k) + 1][k]);  
    int minV = min(dpMin[L][k], dpMin[R - (1 << k) + 1][k]);  
    return maxV - minV; //返回最大值还是最小值,还是最大最小值的差,视情况而定  
}  


例题:POJ3264

RMQ 解法(TIME: 1657MS)

#include  
#include 
#define N 20
using namespace std;
const int maxn = 5e4 + 7;
int ary[maxn], n, q;
int dpMax[maxn][N], dpMin[maxn][N];
void RMQ() {  
    for (int i = 1; i <= n; ++i) {  
        dpMax[i][0] = dpMin[i][0] = ary[i];  
    } 
    for (int j = 1; (1 << j) <= n; ++j) {  
        for (int i = 1; i + (1 << j) - 1 <= n; ++i) {  
            dpMax[i][j] = max(dpMax[i][j - 1], dpMax[i + (1 << (j - 1))][j - 1]);  
            dpMin[i][j] = min(dpMin[i][j - 1], dpMin[i + (1 << (j - 1))][j - 1]);  
        }  
    }  
} 
int queryRMQ(int L, int R) {  
    int k = 0, len = R - L + 1;  
    while ((1 << (k + 1)) <= len)  
        k++;    
    int maxV = max(dpMax[L][k], dpMax[R - (1 << k) + 1][k]);  
    int minV = min(dpMin[L][k], dpMin[R - (1 << k) + 1][k]);  
    return maxV - minV; //返回最大值还是最小值,还是最大最小值得差,视情况而定  
}  
int main()
{
	while(~scanf("%d%d", &n, &q)) {
		for (int i= 1; i <= n; ++i) {
			scanf("%d", ary + i);
		}
		RMQ();
		while(q-- > 0) {
			int L, R;
			scanf("%d%d", &L, &R);
			printf("%d\n", queryRMQ(L, R));
		}
	}
	
	return 0;
} 



线段树解法(TIME: 1563MS)

#include
using namespace std;

const int INF=0x7fffffff;
const int maxn=800010;
int minV=INF;
int maxV=-INF;

struct Node{
	int L , R;
	int minV,maxV;
	int Mid(){
		return (L+R)/2;
	}
}tree[maxn];

void BuildTree(int root,int L,int R){
	tree[root].L=L;
	tree[root].R=R;
	tree[root].minV=INF;
	tree[root].maxV=-INF;
	if(L!=R){
		BuildTree(2*root+1,L,(L+R)/2);
		BuildTree(2*root+2,(L+R)/2+1,R);
	}
}

void Insert(int root,int i,int v){
	if(tree[root].L==tree[root].R){
		tree[root].minV=tree[root].maxV=v;
		return;
	}
	tree[root].minV=min(tree[root].minV,v);
	tree[root].maxV=max(tree[root].maxV,v);
	if(i<=tree[root].Mid()) Insert(2*root+1,i,v);
	else Insert(2*root+2,i,v);
}

void Query(int root,int s,int e){
	if(tree[root].minV>=minV&&tree[root].maxV<=maxV) return;
	if(tree[root].L==s&&tree[root].R==e){
		minV=min(minV,tree[root].minV);
		maxV=max(maxV,tree[root].maxV);
		return;
	}
	if(e<=tree[root].Mid()) Query(2*root+1,s,e);
	else if(s>tree[root].Mid()) Query(2*root+2,s,e);
	else{
		Query(2*root+1,s,tree[root].Mid());
		Query(2*root+2,tree[root].Mid()+1,e);
	}
}
int main(){
	int n,q,h;
	int i,j,k;
	scanf("%d%d",&n,&q);
	BuildTree(0,1,n);
	for(i=1;i<=n;i++){
		scanf("%d",&h);
		Insert(0,i,h);
	}
	for(i=0;i






你可能感兴趣的:(数据结构)