Notations
Range Minimum Query(RMQ)
给定数组A[0, N-1]找出给定的两个索引间的最小值的位置。
Trivial algorithms for RMQ
对每一对索引(i, j),将RMQA(i, j)存储在M[0, N-1][0, N-1]表中。普通的计算将得到一个<O(N3), O(1)> 复杂度的算法。尽管如此,通过使用一个简单的动态规划方法,我们可以将复杂度降低到<O(N2), O(1)>。预处理的函数和下面差不多:
这个普通的算法相当的慢并且使用 O(N2)的空间,对于大数据它是无法工作的。
An <O(N), O(sqrt(N))> solution
现在让我们看看怎样计算RMQA(i, j)。想法是遍历所有在区间中的sqrt(N)段的最小值,并且和区间相交的前半和后半部分。为了计算上图中的RMQA(2,7),我们应该比较A[2], A[M[1]], A[6] 和A[7],并且获得最小值的位置。可以很容易的看出这个算法每一次查询不会超过3 * sqrt(N)次操作。
这个方法最大的有点是能够快速的编码(对于TopCoder类型的比赛),并且你可以把它改成问题的动态版本(你可以在查询中间改变元素)。
Sparse Table (ST) algorithm
一个更好的方法预处理RMQ 是对2k 的长度的子数组进行动态规划。我们将使用数组M[0, N-1][0, logN]进行保存,其中M[i][j]是以i 开始,长度为 2j的子数组的最小值的索引。下面是一个例子
为了计算M[i][j]我们必须找到前半段区间和后半段区间的最小值。很明显小的片段有这2j - 1 长度,因此递归如下:
一旦我们预处理了这些值,让我们看看怎样使用它们去计算RMQA(i, j)。思路是选择两个能够完全覆盖区间[i..j]的块并且找到它们之间的最小值。设k = [log(j - i + 1)].。为了计算RMQA(i, j) 我们可以使用下面的公式
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
#define Min(a,b) (((A[a]) <(A[b])) ? (a) : (b))
#define Max(a,b) (((A[a]) >(A[b])) ? (a) : (b))
#define maxn 50005
int A[maxn];///////////数据
int Mmin[maxn][20];////M[i][j]是以i 开始,长度为 2j的子数组的最小值的索引。 j<log2(maxn)+1;
int Mmax[maxn][20];////M[i][j]是以i 开始,长度为 2j的子数组的最大值的索引。 j<log2(maxn)+1;
void process(int n)//////预处理函数
{
int i,j,k;
int m=(int)floor(log((double)n)/log(2.0));
for(i=0;i<=n;i++)
Mmin[i][0]=Mmax[i][0]=i;
for(j=1;1<<j<=n;j++)///// 2^j<=n
{
k=1<<j;
for(i=1;(i+k-1)<=n;i++)///////i+长度k-1<=总长;
{
Mmin[i][j]=Min(Mmin[i][j-1],Mmin[i+(1<<(j-1))][j-1]);///////////////#define Min();
Mmax[i][j]=Max(Mmax[i][j-1],Mmax[i+(1<<(j-1))][j-1]);///////////////#define Max();
}
}
}
int query(int a,int b)
{
int k=(int)floor(log((double)(b-a+1))/log(2.0));///////log2(b-a+1);
int p=1<<k;
int mmin=min(A[Mmin[a][k]],A[Mmin[b-p+1][k]]);
int mmax=max(A[Mmax[a][k]],A[Mmax[b-p+1][k]]);
return mmax-mmin;
}
int main()
{
int n,q;
int a,b;
int i,j;
while(scanf("%d%d",&n,&q)!=EOF)
{
for(i=1;i<=n;i++)
scanf("%d",&A[i]);
process(n);
while(q--)
{
scanf("%d%d",&a,&b);
printf("%d\n",query(a,b));
}
}
return 0;
}
声明:代码上面的RMQ分析转自:一切随心http://www.cnblogs.com/drizzlecrj/archive/2007/10/23/933472.html
一个比较有趣的点子是把向量分割成sqrt(N)大小的段。我们将在M[0,sqrt(N)-1]为每一个段保存最小值的位置。
M可以很容易的在O(N)时间内预处理。下面是一个例子: