RMQ 算法

       看了一些别人的blog,但是我这次不想做大自然的搬运工——农夫山泉。我想写的接地气一点,别人一眼能看懂。

       先引入话题吧!给你一个数组 ,其中有N个数字,现在给你一次询问,给你区间[l ,r],问你在这个区间内的最大值或最小值为多少?   我们就解决这么一个问题,这就是RMQ的精髓。RMQ算法的作用就是快速求一个区间中的最大值或者最小值。非常适合区间求最值。

OJ题目:http://www.51nod.com/Challenge/ProblemSubmitDetail.html#!#judgeId=746668

       大部分人是这样干的,简单啊!i  从 l 到 r 一路找下去呗,遇见比max大的那就max=arr[i] ;这样子确实可行,但是OJ是通不过的,因为他给的数组范围0-10000,而且会问你  1-2^9  次,那这复杂度就大了去了,超时了昂!下面就是我们的解决思路:

        不用把RMQ想象的多高大上,都一般算法,简单讲就是分治,类似于快速排序。噢,对了,先讲下RMQ吧!

       RMQ(Range Minimum/Maximum Query),即区间最值查询,这是一种在线算法,所谓在线算法,是指用户每次输入一个查询,便马上处理一个查询。RMQ算法一般用较长时间做预处理,时间复杂度为O(nlogn),然后可以在O(1)的时间内处理每次查询。

       有个数组   arr[8]={ 2,7,5,3,6,1,9,0 };

       我们先定义一个二维数组,别问为什么! 如果你要问那肯定动态规划学得不咋样,二维数组这样的数据结构当然是用来动归(动态+递归)呀!  先明白:a[i][j] 的意思是  下标从i开始连续的2^j 个元素中最大(小)的一个,包括第i个元素啊!例如:a[0][2] 就是 2  7  5  3中最大的一个等于 7;ok,现在问我们  a[0][3] 等于多少,当然我们不会选择直接去找0-8里面最大的,我们先看看 a[0][2] 是谁! 再看看 a[4][2] 是谁! 然后在取两者中最大的那个便是a[0][3]即arr 数组区间 [ 0,8 ] 中的最大值!这是举的特例,我们接下来就抽象为普遍通用的思想。

      a[i][j] 是下标为  i , i+1,i+2,... ,i+2^j-1   中的元素的最大值,把它一分为二即下标区间 [ i , i+2^(j-1)-1 ] 与 [  i+2^(j-1) , i+2^j-1 ]  即得到 通用方程:

a[i][j] =  max  (  a [i] [j - 1],  a [ i + 2^(j-1) ] [j - 1])

然后接下来的话就是写代码了,去实现递归逻辑:

首先屡试不爽的递归当然先试一遍啊!那肯定会出现超时,因为递归复杂度有点高了:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
int arr[1001];
int P(int i,int j)
{
	if(j==1)
	{
		return max(arr[i],arr[i+1]);  //2^1就要么是i要么是i+1 
	}
	return max(P(i,j-1),P(i+(1<<(j-1)),j-1)); //这里千万要注意,别写错!! 
}
int main()
{
	int N;
	int Q;
	cin>>N;
	for(int i=0;i<N;i++)
	cin>>arr[i];
	cin>>Q;
		while(Q--)
		{
			int a,b;
			cin>>a>>b;
			int max1,max2;
			int k=log2(b-a+1); 
			//将 [a,b] 区间转换为两个 定义区间,中间会有重叠也不要紧,因为我们要求最值。 
			max1=P(a,k);
			max2=P(b-(1<<k)+1,k);
			cout<<max(max1,max2)<<endl;
		}
	return 0;
} 

我好想偷懒哦,能不能改动少量代码改进复杂度呢!为偷懒而思考一下吧! 诶,还真可以:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
int arr[10010];
int answer[10010][20]; 
int P(int i,int j)
{
	if(j==1)
	{
		return max(arr[i],arr[i+1]);  //2^1就要么是i要么是i+1 
	}
	if(answer[i][j-1]==0)
	answer[i][j-1]=P(i,j-1);
	if(answer[i+(1<<(j-1))][j-1]==0)
	answer[i+(1<<(j-1))][j-1]=P(i+(1<<(j-1)),j-1);
	
	return max(answer[i][j-1],answer[i+(1<<(j-1))][j-1]); //这里千万要注意,别写错!! 
}
int main()
{
	int N;
	int Q;
	cin>>N;
	for(int i=0;i<N;i++)
	cin>>arr[i];
	memset(answer,0,sizeof(answer));
	cin>>Q;
		while(Q--)
		{
			int a,b;
			cin>>a>>b;
			int max1,max2;
			int k=log2(b-a+1); 
			//将 [a,b] 区间转换为两个 定义区间,中间会有重叠也不要紧,因为我们要求最值。 
			max1=P(a,k);
			max2=P(b-(1<<k)+1,k);
			cout<<max(max1,max2)<<endl;
		}
	return 0;
} 

算了,送佛送到西,下面递推也能快速实现:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
int arr[10010];
int answer[10010][20]; 
void P(int N)
{
	int k=log2(N); 
	for(int i=0;i<N;i++)
	answer[i][0]=arr[i];//初始条件,第一列数组已知 
	
	for(int j=1;j<=k;j++)
	for(int i=0;i+(1<<j)-1<=N;i++)
	answer[i][j]=max(answer[i][j-1],answer[i+(1<<j-1)][j-1]);
}
int RMQ(int a,int b)
{
	int max1,max2;
	int k=log2(b-a+1); 
	max1=answer[a][k];
	max2=answer[b-(1<<k)+1][k];
	return max(max1,max2);
}
int main()
{
	int N;
	int Q;
	cin>>N;
	for(int i=0;i<N;i++)
	cin>>arr[i];
	P(N);
	cin>>Q;
	while(Q--)
	{
		int a,b;
		cin>>a>>b;
		printf("%d\n",RMQ(a,b));
	}
	return 0;
} 

       

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

矩阵科学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值