RMQ算法是一种查找一个区间最值的算法,当然是有Q次询问,如果只询问一次,当然直接遍历就好了,
如果是询问很多次,这时就需要RMQ算法了。
RMQ算法
RMQ算法用的是DP求解, 预处理是nlogn的,查询是O(1)。
A[i]表示要查询的数列,F[i,j]表示从i开始2^j个数中最大的那一个。
例如: 3 ,4 ,5,23,1,12,7
F[1,0]就表示从3开始的1个数,就是3本身,F[2,2]表示从4开始2^2个数,即4,5,23,1这四个数中最大值23.
那么DP的初始状态是什么?
就是每个数的最大值是其本身,即F[i,0],把F[i,0]先求出来就是DP的初态
那么DP的状态转移方程呢?
通过二分的思想很容易得出,F[i,j]=max(F[i,j-1],F[i+(1<<(j-1)),(j-1)].
即把从i开始的2^j个数分成两个区间,分别是 [ i,i+2^(j-1)-1 ]和[ i+2(j-1),i+2^j-1],找出这两个区间中最大值进行比较。
void rmq(int n)
{
for(int i=1;i<20;i++)
for(int j=1;j+(1 << i)-1<=n;j++) //j代表从j开始分为(j,j+2^(i-1)-1),(j+2^(i-1),j+2^i)这两个区间比较
{
maxnum[j][i]=max(maxnum[j][i-1],maxnum[j+1<<(i-1)][i-1]);
minnum[j][i]=min(minnum[j][i-1],minnum[j+1<<(i-1)][i-1]);
}
}
需要注意的问题:
1、注意for循环的嵌套,不能把j那一层放到外面,如果放到外面,就成了先求
F[1,1],F[2,1],F[3,1]......
给出一个序列:3 ,4 ,5,23,1,12,7
F[1,1]=4,F[2,1]=5,F[3,1]=23,乍一看好像也没什么问题,接下来继续循环
F[1,2]=4,F[2,2]=5,F[3,2]=23,应该看出来问题来了, 找从i开始的4个数,只有前一半是更新出最值来了,后一半还是0.
所以不能这样循环。必须先把每个位置的值都从j=1(2个数的最值)开始依次更新,再是j=2(4个数的最值),3(8个数的最值)......
2、注意一下<< 和+,-运算符的优先级(在这踩了好长时间的坑。。。)
例如 1+2<<2
答案是3*2*2=12,所以想要表达成我们想要的算式则是:1+(2<<2).
查询过程
可以看成指数的逆运算(我是这么理解的),例如[l,r]这个区间,一共有r-l+1个数,那么就是k=log2(r-l+1),
那么要求的最大值就是 max([l,k],[r-(1<<k)+1,k],
最小值同理。
while(q--){
scanf("%d%d",&l,&r);
int k=(int)(log(r-l+1.0)/log(2.0));
int maxn=max(maxnum[l][k],maxnum[r-(1<<k)+1][k]);
int minx=min(minnum[l][k],minnum[r-(1<<k)+1][k]);
printf("%d\n",maxn-minx);
}
例题:poj3264