基于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
View Code1 #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 }
•预处理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);