【RMQ】【Sparse_Table算法】

Posted on 2015-09-02 00:06  LLGemini  阅读(278)  评论(0编辑  收藏  举报

 摘自网友,具体哪个忘记了,抱歉~

 

定义:

RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:

  对于长度为n的数列A,回答若干询问RMQ(A,i,j) (i,j<=n),返回数列A中下标在i,j之间的最小/大值。

此类问题的解决方法有很多,暴力(当然也就说说,基本没有出题的会让你暴过去)、线段树(复杂度为O(nlogn))、以及一个非常有名的在线处理RMQ问题的算法 -- Sparse_Table算法,简称ST算法,该算法能在进行O(nlogn)的预处理后达到查询O(1)的效率。主要思想为dp。

 

(一)预处理,DP

设A[i]是要求区间最值的数列,dp[i][j]表示从第i个数起连续2j个数中的最大值

例如有A[10] = 3 2 4 5 6 8 1 2 9 7;

初值:

dp[1][0]表示第1个数起,长度为20=1的最大值,即元素3。同理 dp[1][1] = max(3,2) = 3, dp[1][2]=max(3,2,4,5) = 5,dp[1][3] = max(3,2,4,5,6,8,1,2) = 8;

并且我们可以容易的看出dp[i][0] = A[i]。

状态转移方程:

我们把dp[i][j]分成长度相同且为2j-1的两段,一段为dp[i][2j - 1], 一段为dp[i+2j - 1][2j - 1]。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。

dp[i,j]就是这两段各自最大值中的最大值。于是我们得到了状态转移方程:

  dp[i][j] = max(dp[i][j-1], dp[i + 2(j-1)][j-1];

 

1 /*预处理->O(nlogn)*/
2 int dp[maxn][20];
3 void initRMQ(int n)
4 {
5     for(int i = 1; i <= n; i++) dp[i][0] = A[i];
6     for(int j = 1; j <= 20; j++)
7         for(int i = 1; i + (1 << (j-1)) <= n; i++)
8             dp[i][j] = max(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
9 }

  注意代码中循环的顺序,外层是j,内层是i!!

 

(二)查询

若查询区间为A[i, j],那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可重叠,比如查询A[5,9],我们可以查询A[5678]和A[6789])。

因为这个区间的长度len = j - i + 1,所以我们可以取k=log2(len),则有:RMQ(A, i, j) = max{ dp[i][k], dp[j-2k+1][k] }。

例如,查询区间[2,8]的最大值,k = log2(8 - 2 + 1) = 2,即求max( dp[2][2],dp[8 - 2 ^ 2 + 1][2]) = max(F[2, 2],F[5, 2]);

 1 int Log2[maxn];
 2 void init()
 3 {
 4     Log2[0] = -1; Log2[1] = 0;
 5     int i, j;
 6     for(i = 2, j = 0; i < maxn; i++)
 7     {
 8         if(i > (1<<(j+1))) j++;
 9         Log2[i] = j;
10     }
11 }
12 
13 int query(int L, int R)
14 {
15     if(L > R) swap(L, R);
16     if(L == R) return dp[L][0];
17     int len = R-L+1;
18     int k = Log2[len]; //此处用的是Log2[]数组保存每个数的log值,当然也可以用下面的式子求得;
19     //int k = (int)(log(R - L + 1.0) / log(2.0));
20     return max(dp[L][k], dp[R-(1<<k)+1][k]);
21 }

 

RMQ常常应用在字符串问题里.