看了一些别人的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;
}