RMQ问题:求长度为n的数列中,求[i,j]直接的最值。
ST算法:一种动态规划的方法。
一、预处理dp数组
对于区间[i,i+2^j-1]的最值,只需要知道区间[i,i+2^(j-1)-1]和区间[i+2^(j-1),i+2^j-1]的最值即可。
由此可的递推方程:dp[i,i+2^j-1] = max(dp[i,i+2^(j-1)-1],dp[i+2^(j-1),i+2^j-1])
但是对于一个比较长的数列,2^j是一个非常大的数,我们也可发现,没什么必要直接记录左右端点。
优化为i记录一个起点,j记录类似距离的东西,dp[i,j]表示区间[i,i+2^j-1]。
优化后递推方程:dp[i,j] = max(dp[i,j-1],dp[i+2^(j-1),j-1])
预处理dp数组时间复杂度为O(nlogn)。
二、查询最值
所以开始把一个区间当dp数组求出来,再进行查询即可。
但是查询时候,知道的是两个端点l,r。
对于区间[l,r],如何再dp数组中查询呢?
前面讲了i表示起点,j代表类似距离的东西。
很明显l就是i了。可是2^j-1不可能是r,这时候就要找个中间的"j"了。
令len = r-l+1, 则2^k <= len(此处注意不是2^k-1,反证:当2^k-1 == len时,l + 2^k-1 = l + len = r + 1 > r)
即[l,r] = max(dp[l,k],dp[r-(2^k)+1,k])
查询时间复杂度为O(1)。
尽管代码比较简洁并且功能比较强大,速度也比较快,可是并没有线段树那么功能多。
1 #include <cstdio> 2 #include <cmath> 3 #include <iostream> 4 using namespace std; 5 const int maxn = 100005; 6 int ma[maxn][20]; 7 int mi[maxn][20]; 8 int a[maxn]; 9 void init(int n){ 10 for(int i = 1; i <= n; ++i) 11 ma[i][0] = mi[i][0] = a[i]; 12 for(int j = 1; (1<<j) <= n; ++j) 13 for(int i = 1; i+(1<<j)-1 <= n; ++i){ 14 ma[i][j] = max(ma[i][j-1], ma[i+(1<<(j-1))][j-1]); 15 mi[i][j] = min(mi[i][j-1], mi[i+(1<<(j-1))][j-1]); 16 } 17 } 18 int rmq_max(int l, int r, int k){ 19 return max(ma[l][k], ma[r-(1<<(k))+1][k]); 20 } 21 int rmq_min(int l, int r, int k){ 22 return min(mi[l][k], mi[r-(1<<(k))+1][k]); 23 } 24 int rmq(int l, int r){ 25 int k = 0; 26 while(1<<(k+1) <= r-l+1) 27 ++k; 28 //int k = (int)(log(1.0*(r-l+1))/log(2.0));//也可以直接算出k 29 //算出k后,具体情况进行调用函数计算。例如返回最大差值. 30 return rmq_max(l,r,k) - rmq_min(l,r,k); 31 } 32 int main(){ 33 int n,q; 34 scanf("%d%d",&n,&q);//n个数,q次查询次数 35 for(int i = 1; i <= n; ++i)//输入n个数 36 scanf("%d",a+i); 37 init(n); int l,r; 38 while(q--){//进行q次查询 39 scanf("%d%d",&l,&r); 40 printf("%d ",rmq(l,r)); 41 } 42 return 0; 43 }