基于ST表格的RMQ

 

参考资料

  [1]:http://dongxicheng.org/structure/lca-rmq/

 

•前言

  RMQ(Range Minimum/Maximum Query),即区间最值查询。

  假设需要查询的区间为[a,b],对于此问题,有三种方法可以得到答案:

  (1):暴力

  (2):线段树

  (3):ST(Sparse Table)算法

我对ST算法的理解

  ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

  (1):首先是预处理,用动态规划(DP)解决。

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

  例如数列:

    i: 1 2 3 4 5 6 7 8 9

  a[i]: 3 2 4 5 6 8 1 2 9

  dp[0][1]表示自第1个数起,长度为20=1的最大值,其实就是3这个数。

  dp[0][2]表示自第2个数起,长度为20=1的最大值,其实就是2这个数。

  ............

  dp[0][9]表示自第9个数起,长度为20=1的最大值,其实就是9这个数。

  dp[1][1]=3,dp[1][2]=4,..........

  从这里可以看出dp[0][ i ]其实就等于a[i]。

  这样,DP的状态、初值都已经有了,剩下的就是状态转移方程。

  我们把dp[ i ][ j ]平均分成两段(因为dp[ i ][ j ]一定是偶数个数字(2i 个) ),前 2i-1 个数字为一段,后 2i-1 个数字为一段。

  即将区间[ j ,j+2i-1] 分成了区间[ j ,j+2i-1-1]和区间[ j+2i-1 , j+2i-1],且各含有 2i-1 个数

  用上例说明,当i=3,j=1时就是3,2,4,5 和 6,8,1,2这两段。

  dp[ i ][ j ]就是这两段的最大值中的最大值。

  于是我们得到了动态规划方程dp[ i ][ j ]=max( dp[i-1][ j ],dp[i-1][ j+2^(i-1)] );

  (2):然后是查询,查询区间[a,b]的最大值。

  区间[a,b]上共有(b-a+1)个数,满足2k ≤ (b-a+1)的最大的k为:k=log2(b-a+1)(以 2 为底,(b-a+1)的对数)

  但在 c 语言的<math.h>中也含有log(),不过是以 e 为底的,故需要将上述以2为底的对数变一下形,根据换底公式有 log2(b-a+1)=ln(b-a+1)/ln(2);

  转化成c语言语法 k = log(b-a+1)/log(2),而从 (a 开始的前 2k 个数 + 从 b 开始的后 2k 个数)一定包含了[a,b]区间的所有数。

  所以 max( [a,b] ) = max( dp[k][a] , dp[k][b-2^k+1] );

•Code

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 using namespace std;
 5 
 6 int n;//共 n 个数,此处 n = 9
 7 int a[10]={0,3,2,4,5,6,8,1,2,9};//下标从 1 开始
 8 struct Node
 9 {
10     int dp[4][10];//从 1 开始,最多向后连续9个数,而 2^4 > 9,故此处第一个参数为 4 足矣
11     void Preset()//预处理出从第 i 个数开始连续 1 个数的dp[][]值,也就是 a[i]本身
12     {
13         for(int i=1;i <= 9;++i)
14             dp[0][i]=a[i];
15     }
16     void ST()//处理出 ST 表
17     {
18         for(int i=1;i < 4;++i)
19             for(int j=1;j <= (n-(1<<i)+1);++j)//n-(1<<i)+1之后位置的数向后没会有 2^i 个数,所以需要满足 j<=(n-(1<<i)+1),确保位置 j 及其之后的位置含有 2^i 个数
20                 dp[i][j]=max(dp[i-1][j],dp[i-1][j+(1<<(i-1))]);//区间[j,j+2^i-1]被分割成了个数相等的两部分,并从中取最大值
21     }
22     int RMQ(int a,int b)//区间[a,b]最大值查询
23     {
24         if(a > b)
25             swap(a,b);
26         int k=log(b-a+1)/log(2);
27         return max(dp[k][a],dp[k][b-(1<<k)+1]);
28     }
29 }_rmq;
30 int main()
31 {
32     int a,b;
33     n=9;
34     while(scanf("%d%d",&a,&b))
35     {
36         _rmq.Preset();
37         _rmq.ST();
38         printf("%d\n",_rmq.RMQ(a,b));
39     }
40     return 0;
41 }
View Code

预处理log2()的技巧

  log2[(1000)2] = 3;

  log2[(100)2] = 2 , log2[(101)2] = 2 , log2[(110)2] = 2 , log2[(111)2] = 2;

  发现没,(111)2是三位二进制数的最大值,其与四位二进制数的最小值(1000)2相与的结果等于0;

  而(111)2与任意一个三位二进制数相与,结果都不为0;

  所以,定义lb[ i ] 表示log2(i)的结果;

1 lb[0]=-1;
2 for(int i=1;i < maxn;++i)
3     lb[i]=lb[i-1]+((i&(i-1)) == 0 ? 1:0);

 

posted @ 2018-10-18 15:19  HHHyacinth  阅读(443)  评论(0编辑  收藏  举报