RMQ问题

参考链接 http://kmplayer.iteye.com/blog/575725

               http://blog.163.com/zhaohai_1988/blog/static/209510085201263011135062/

 

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

主要方法及复杂度(处理复杂度和查询复杂度)如下:
1.朴素(即搜索) O(n)-O(n)  (可以优化成大小桶算法)
2.线段树(segment tree) O(n)-O(qlogn)
3.ST(实质是动态规划) O(nlogn)-O(1)

线段树方法:
线段树能在对数时间内在数组区间上进行更新与查询。
定义线段树在区间[i, j] 上如下:
第一个节点维护着区间 [i, j] 的信息。
if i<j , 那么左孩子维护着区间[i, (i+j)/2] 的信息,右孩子维护着区间[(i+j)/2+1, j] 的信息。
可知 N  个元素的线段树的高度 为 [logN] + 1(只有根节点的树高度为0) .
下面是区间 [0, 9]  的一个线段树:



线段树和堆有一样的结构, 因此如果一个节点编号为 x ,那么左孩子编号为2*x  右孩子编号为2*x+1.

例题:http://blog.csdn.net/gk1988/article/details/8920814

 

Sparse-Table算法

 ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

首先是预处理,用动态规划(DP)解决。设A[i]是要求区间最值的数列,F[i, j]表示从第i个数起连续2^j个数中的最大值。例如数列3 2 4 5 6 8 1 2 9 7,F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。 F[1,2]=5,F[1,3]=8,F[2,0]=2,F[2,1]=4……从这里可以看出F[i,0]其实就等于A[i]。这样,DP的状态、初值都已经有了,剩下的就是状态转移方程。我们把F[i,j]平均分成两段(因为f[i,j]一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。F[i,j]就是这两段的最大值中的最大值。于是我们得到了动态规划方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])。

然后是查询。取k=[log2(j-i+1)],则有:RMQ(A, i, j)=min{F[i,k],F[j-2^k+1,k]}。 举例说明,要求区间[2,8]的最大值,总共2到8是7个元素,所以k=2,那么就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和f[5,2]得到。

具体如下图所示:

   说明:M[1][0]的值就是A[1]的结果,M[2][0]的值就是A[2]的结果,M[1][1]的值就是M[1][0]、M[2][0]的结果   (1)
              M[3][0]的值就是A[3]的结果,M[4][0]的值就是A[4]的结果,M[3][1]的值就是M[3][0]、M[4][0]的结果   (2)
              ………………
              在计算出(1) (2)的前提下,可以算出M[1][2]的值,M[1][1]、M[3][1]两者的结果
               ………………
 
算法伪代码
//初始化 INIT_RMQ 
//max[i][j]中存的是重i开始的2^j个数据中的最大值,最小值类似,num中存有数组的值

 for i : 1 to n   
	 max[i][0] = num[i] 
	 
 for j : 1 to log(n)/log(2)  
	 for i : 1 to (n+1-2^j)     
		 max[i][j] = max(max[i][j-1], max[i+2^(j-1)][j-1])
		 
//查询 RMQ(i, j) 
 
 k = log(j-i+1) / log(2) 
 return MAX(max[i][k], max[j-2^k+1][k])
 
大小桶算法:

大小桶算法是在朴素比较算法的基础上优化,减小了比较的次数,其时间取决于桶大小及容量

基本思想:
将A[]分成bucketnum个桶,每个桶包含capacity个数,每个桶维护一个最大(小)值
RMQ(A, srart, end)查询包含两种情况:
1、start, end落在同一个桶或者相邻桶,刚直接从i到j挨个查询取最值 (数据量少)
2、start, end落在不同的桶,假设start落在第i个桶,end落在第j个桶,
   则分别计算第i个桶start到桶尾的最值、i、j之间桶的最值(不包括i,j直接比较每个桶维护的最值)
   以及第j个桶桶头到end的最值,最后在这三个结果中取最值

例题:http://poj.org/problem?id=3264

Source Code

/*
大小桶(将MAX分成bucketnum段,每段放capacity个数)
*/
#include<iostream>          
using namespace std;
#define Max 50000          
#define capacity 250    //桶容量
#define bucketnum 200      //桶的个数

//bucketmax保存桶的最大值,bucket保存桶的最小值
int score[Max+1],bucketmax[bucketnum+1],bucketmin[bucketnum+1];

int main()
{
	int n,m,i,num1,num2,result1,result2;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		//初始化,不然会影响下一次测试
		memset(bucketmax,0,sizeof(bucketmax));
		for(i=0;i<bucketnum;i++)
			bucketmin[i]=10000000;     

		for(i=0;i<n;i++)
		{
			scanf("%d",&score[i]);

			//找出一段内最大的值存入BUCKETMAX,最小的存入BUCKETMIN
			if(bucketmin[i/capacity]>score[i])
                bucketmin[i/capacity]=score[i];
			if(bucketmax[i/capacity]<score[i])
                bucketmax[i/capacity]=score[i];      
		}
		while(m--)
		{
			scanf("%d %d",&num1,&num2);	    

			//left指示左边的数落在的区间,right指示右边的数在的区间
			int left=(num1-1)/capacity;
			int right=(num2-1)/capacity;
			result1=-1,result2=10000000; 
			
            //如果两个数在同一区间,则在两个数之间找最大最小
			if(left==right)          
			{
				for(i=num1;i<=num2;i++)
					if(result1<score[i-1])
						result1=score[i-1];
					for(i=num1;i<=num2;i++)
						if(result2>score[i-1])
							result2=score[i-1];
			}
			else    //如果不在同一区间
			{      
				//找NUM1所在的区间,一个数一个数的找
				for(i=num1;i<=(left+1)*capacity;i++)
					if(result1<score[i-1])
						result1=score[i-1];  	
				for(i=num1;i<=(left+1)*capacity;i++)
					if(result2>score[i-1])
						result2=score[i-1];

                //直接比较整段的最大值BUCKETMAX[i],最小值BUCKETMIN[i]
				for(i=left+1;i<right;i++)
					if(result1<bucketmax[i])
						result1=bucketmax[i];   			
				for(i=left+1;i<right;i++)
					if(result2>bucketmin[i])
						result2=bucketmin[i]; 
				
				//找NUM2所在的区间,一个一个的找
				for(i=right*capacity;i<=num2;i++)
					if(result1<score[i-1])
						result1=score[i-1];    
				for(i=right*capacity;i<=num2;i++)
					if(result2>score[i-1])
						result2=score[i-1];
			}
			printf("%d\n",result1-result2);
			
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值