RMQ(Range Minimum/Maximum Query)问题是指对于长度为n的数列weight,回答若干询问RMQ(weight,l,r)(l,r<=n),返回数列A中下标在i,j里的最小(大)值。
假设询问m次,如果对于每个询问都扫描对应的区间,找到最值,那么最坏情况和平均情况下都是O(nm)的时间复杂度。所以需要事先进行一些预处理,使得一些重复计算的东西不再重复计算,才能够将复杂度降低下来。如果先预先计算一些区间的最值,然后把每个询问都拆成若干个计算了最值的区间并统计这些区间的最值的最值,就可以得出答案。根据二分法的思想,将统计的区间规定为所有长度为2的非负整数次幂的区间。
用rmq[i][j]表示从第i个数开始后面2^j个数的最值,很显然rmq[i][0]等于weight[i],j的最大值为(int)((log(N))/(log(2.0)))。递推关系为rmq[i][j]=min(rmq[i][j-1],rmq[i+(1<<(j-1))][j-1])。可以看到,这是一个动态规划问题。对于每一次询问,j的最大值mi只能取(int)((log(r-l+1))/(log(2.0)))。为了防止超出边界,答案应该写成min(rmq[l][mi],rmq[r-(1<<mi)+1][mi])而不是min(rmq[l][mi],rmq[l+(1<<mi)][mi])。
以hihoCoder1068为例给出代码:
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1e6+5; const int max_pos=20; struct Quary { int l,r; }quary[maxn]; int N,Q; int weight[maxn]; int rmq[maxn][max_pos]; int main() { scanf("%d",&N); for(int i=1;i<=N;i++) scanf("%d",&weight[i]); scanf("%d",&Q); for(int i=1;i<=Q;i++) scanf("%d%d",&quary[i].l,&quary[i].r); memset(rmq,0x5f,sizeof(rmq)); for(int i=1;i<=N;i++) rmq[i][0]=weight[i]; int l=(int)((log(N))/(log(2.0))); for(int j=1;j<=l;j++) { for(int i=1;i+(1<<j)-1<=N;i++) { rmq[i][j]=min(rmq[i][j-1],rmq[i+(1<<(j-1))][j-1]); } } for(int i=1;i<=Q;i++) { int l=quary[i].l; int r=quary[i].r; int mi=(int)((log(r-l+1))/(log(2.0))); printf("%d\n",min(rmq[l][mi],rmq[r-(1<<mi)+1][mi])); } }