ST表超级详解

Posted soul-maker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ST表超级详解相关的知识,希望对你有一定的参考价值。

 ST表超级详解

       关于ST表,有很多文章,这里本蒟蒻也来发一波~~ 希望能为您提供帮助~~

     1.ST表的介绍

     ST表算法全称Sparse-Table算法,是由Tarjan提出的一种解决RMQ问题(区间最值)的强力算法。离线预处理时间复杂度 θ(nlogn),在线查询时间 θ(1),可以说是一种非常高效的算法。不过ST表的应用场合也是有限的,它只能处理静态区间最值,不能维护动态的,也就是说不支持在预处理后对值进行修改。一旦修改,整张表便要重新计算,时间复杂度极高。动态最值可以用线段树、树状数组等来维护。

     2.ST表的一般实现

     ST表之所以广为使用,不仅因为它的时间复杂度优秀,还因为它的代码思路比较清晰,码量比较合适。核心的代码主要集中预处理和查询上。那么接下来我们就先来讲讲它的预处理(下面都以区间最小值来讲,最大值可以类推)。st[i][j]表示从第 i 个数起向后连续2^j个数中的最小值。注意,是包括第 i 个数在内的2^j个数。那么,我们可以得到转移方程:

     1.st[i][0]=第i个数(很明显,2^0==1,从第i个数起向后数1个数,当然只有它自己。)

     2.st[i][j]=min{ st[i][j-1] , st[i+(1<<j-1)][j-1]}(j≠0)(其实也挺好理解的。请看下图:)

                                                      技术图片

      (把整个2的j次方区间分成两个2^(j-1)区间,那么整个大区间的最小值就是两个已计算过的区间最小值的最小值。第一个区间很明白,是st[i][j-1],就是从左端点 i 起向右数2^(j-1) 个数。第二个区间长度还是2^(j-1),但是左端点需要由 i 加上上一个区间的长度,毕竟它们是连在一起的。1<<j-1 就是2^(j-1)。)

    以上,就是ST表的初始化部分。

    接下来,我们来讲讲查询。假设每次查询的左右端点分别为le和ri。思路:首先找到一个值k,k的含义是“最大的2^k不超过查询区间长度(ri-le+1)”。举个例子:le=1,ri=9,那么k最大为3,2^k==8,不超过区间长度。那么我们找两个长度为2^k的区间,一个以le为左端点,一个以ri为右端点。由于k最大,所以可以肯定,这两个小区间一定覆盖了整个大区间(一定的!!!不信可以自己试验)。那么,虽然两个小区间会有重叠部分,但是这里要求的是最小值,而非求和,所以这两个小区间最小值中的最小值,一定是整个区间的最小值!

    再细一点的说,一个区间为st[le][k],还有一个为st[ri-(1<<k)+1][k](这个能理解吧?1<<k就是2^k),那么:

    查询函数query(le,ri)=min{st[le][k],st[ri-(1<<k)+1][k]}。再次强调:重叠的小区间不影响整个区间的最小值!

    再总结一下查询的过程:1.找到k    2.返回两个st之间的最值  3.美妙结束。

    到这里,ST表的一般实现已被剖析完毕。有意思吧?

    3.ST表的重点思想拆解及注意事项

    ST表的确是一个不俗的数据结构。它背后运用到的算法思想也值得我们深究。主要有动态规划和倍增。在进行初始化的时候,运用了动归思想,逐步完善st数组。在整个算法中,倍增思想都一直贯彻其中,使整个算法的时间复杂度降了一个log级别,非常高效。在查询的时候,利用到了最小值不受区间重叠干扰的性质,所以可以巧妙地使用倍增。精髓!

    还有以下注意事项要提一提:1.在定义st数组的时候,第一维定义元素个数,那第二维的j怎么定义呢?还是很简单,j就像上文的k一样,定为“最大的2^j不超过最多元素个数”。如最多100000个数,那么j最大16,2^16==65536, 不超过100000。2.ST表的实践其实很广,很多时候会作为其他算法的辅助,应仔细思考。3.ST表代码中,对区间的边边角角操纵较多,需要以清醒的头脑对待,不要混了。

    4.上代码!!

    说了这么多,不看一下代码还是不行滴~~献上我丑陋的代码!

 

 1 #include<bits/stdc++.h>
 2 #define gc getchar() 
 3 #define MX 100005
 4 using namespace std;
 5 int m,ans[MX],st[MX][16];
 6 void q_in(int &x){
 7     x=0;int f=1;char c=gc;
 8     while(!isdigit(c)){if(c==-)f=-1;c=gc;}
 9     while(isdigit(c)){x=(x<<3)+(x<<1)+c-48;c=gc;}
10     x*=f;
11 }//读入优化
12 void getrd(){
13     for(int j=1;(1<<j)<=m;j++)
14         for(int i=1;i+(1<<j)-1<=m;i++)
15             st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
16 }//预处理
17 int query(int le,int ri){
18     int k=0;while(le+(1<<k+1)-1<=ri)k++;
19     return min(st[le][k],st[ri-(1<<k)+1][k]);
20 }//查询
21 int main(){
22     int n;q_in(m);q_in(n);
23     for(int i=1;i<=m;i++)q_in(st[i][0]);
24     getrd();
25     for(int i=1;i<=n;i++){
26         int le,ri;q_in(le);q_in(ri);ans[i]=query(le,ri);
27     }
28     for(int i=1;i<=n;i++)cout<<ans[i]<< ;//ans保留每次查询的答案
29     return 0;
30 } 

 

    谢谢大家!!!

        

以上是关于ST表超级详解的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ2006[NOI2010]超级钢琴 ST表+堆

2006: [NOI2010]超级钢琴|ST表|堆

[BZOJ 2006][NOI2010]超级钢琴(ST表+堆)

算法详解——st表

bzoj 2006 [NOI2010]超级钢琴 堆 + ST表

[NOI2010][bzoj2006] 超级钢琴 [主席树/ST表+堆]