RMQ问题之ST算法

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\n",rmq(l,r));
41     }
42     return 0;
43 }
ST算法

 

posted @ 2017-10-19 09:38  Posase  阅读(186)  评论(0编辑  收藏  举报