深入理解倍增算法与ST表

RMQ问题:给出一个序列,然后多次询问某一个区间的最大值。

一种显然的暴力:每次O(N)遍历,总O(NM),显然会超时。
ST表:
O(NlogN)对一个序列进行预处理,每次O(1)查询给定区间的最值,总O(NlogN+M)。
思路:
预处理:
令f[j][i]表示区间[j,j+2^i-1]内的最大值,
显然f[i][0]=a[i]
从小到大枚举i从1~20,
有f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1])
从DP的观点看,是从i-1推出i。
事实上,每次所需处理的区间长度均为上一次的2倍,所以由上一次的区间最值一定可以推出这一次的区间最值。

用线段清晰地表示处理过程
i /已确定答案的区间

0 -      -      -      -      -      -      -      -

1 --
     --
       --
         --
....  
2 ----
    ----
     ----
....
3 --------

求值:
由上图可以看出,其实并没有处理“任意区间”,为什么可以回答任意询问呢?
因为最值具有“可重复贡献的性质”,
只要我们找出两个的区间,它们的并集恰好等于询问区间,则两个区间最值的最值就是询问区间的最值。
具体地:从左端点开始往右找,从右端点往左找
令s=log2(r-l+1)
则ans=max(a[l][s],a[r-(1<<s)+1][s])
理想情况下,两个区间都正好是上图中“已处理的区间”
其他情况下,因为log2是下取整,这样做保证了不会第一个查询超过范围,而第二个范围里-(1<<s)+1的绝对值严格等于2^s-1,恰好到右端点。
另外,s在向下取整时至多不会比真实值小1,则两个区间长度均为l-r+1的一半以上,所以[l,r]一定被完整覆盖。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,f[200005][21],l,r;
 4 int main(){
 5   cin>>n>>m;
 6   for(int i=1;i<=n;i++)cin>>f[i][0];
 7   for(int i=1;i<=20;i++)
 8     for(int j=1;j+(1<<i)<=n+1;j++)
 9       f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
10   for(int i=1;i<=m;i++){
11     cin>>l>>r;
12     int s=log2(r-l+1);
13     cout<<max(f[l][s],f[r-(1<<s)+1][s])<<endl;
14   } 
15 } 

 

posted @ 2020-03-18 13:06  _vv123  阅读(263)  评论(0编辑  收藏  举报