ST表,適用於解決RMQ(區間最值問題),相似於線段樹和樹狀數組這兩個算法node
ST表相比於線段樹,預處理的複雜度與線段樹同樣,而查詢的複雜度則大大快於線段樹ios
預處理 | 查詢 | |
ST表 | O(nlogn) | O(1) |
線段樹 | O(nlogn) | O(logn) |
洛谷 P3865 【模板】ST表git
ST表:算法
線段樹(不開O2):數組
線段樹(開O2優化): ide
經比較,咱們能夠垂手可得的看出,當咱們解決RMQ問題時,ST表的速度明顯優於線段樹函數
ST表的主體是一個二維數組:st[i][j]。表示查詢的數組的下標i到i+2^j-1優化
如何進行預處理呢??ui
首先,咱們把從0~n-1的2^0的部分進行覆蓋spa
而後向下處理
Q:處理??怎麼處理??
A:不明白??那咱們來舉一個例子
咱們以一個長度爲5的數組
2^0部分覆蓋過去就是 a[1],a[2],a[3],a[4],a[5]
2^1部分的長度爲4,也就是下標從1到4,
st[0][1]是下標爲0~1的區間的最值
也就是st[0][0]和st[0][1]的最值
以此往下類推
結論:st[i][j] = min ( st[i][j-1] , st[i + 2^(j-1) ][j-1] );
預處理函數(求最大值):
inline void init(int n) { for(int i=1;i<=n;i++) { st[i][0]=a[i]; } for(int j=1;(1<<j)<=n;j++) { for(int i=1;i+(1<<j)-1<=n;i++) { st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]); } } }
查詢函數,這個地方就很難理解了
首先,初始化,初始化的時候,每個狀態對應的區間都是2^j
因爲查詢時的長度不知道
因此咱們要用到一個定理:2^log(a)>a/2
由於log(a)函數表示是小於等於2的最大次方
也就是說,a>=2^(max_j)
其中2^max_j表示的是不大於a的2的最大次方
Q:不明白。。
A:不要緊,那咱們來看幾個例子就知道了
log(4)=2; log(5~7)=2; log(8)=3; log(9~15)=3; log(16)=4; log(17~31)=4;....
Q:可窩仍是不明白。
A:不要緊,咱們再以log(4)=2,log(5~7)=2爲例
這些數的log都是2
再仔細琢磨一下log()裏面的數
4,5,6,7。floor(sqre(4)),floor(sqre(5)),floor(sqre(6)),floor(sqre(7))它們的值都等於2。
也就是2^max<=a。
那麼咱們要查詢x到y的最大值
咱們設len=(y-x+1),data=log(len)=log(y-x+1)
根據上面的定理,咱們能夠知道,2^data>len/2
從下標(位置)上來看,x+2^data>(x+y)/2;
也就是越過了區間[x,y]的中間
由於位置超過了一半
因此x到y的最大值就能夠表示爲max(從x日後的2^t的最大值,從y往前2^t的最大值);
前面的狀態咱們能夠表示爲 st[data][x]
設後面的初始位置爲k,也就是說,從y往前2^t的最大值的初始位置是k
那麼k+2^data-1=y;
因此k=y-2^data+1;
因此後面的狀態就是st[data][y-2^data+1]
因此說,區間[x,y]的最大值就能夠表示爲max(st[data][x],st[data][y-2^t+1])
因此查詢的時間複雜度是O(1)
查詢函數:
inline int search(int l,int r) { int k=(int)(log((double)(r-l+1))/log(2.0)); return max(st[l][k],st[r-(1<<k)+1][k]); }
一道ST表模板題,(上文也有提到)
能夠垂手可得的看出ST表和線段樹在處理RMQ問題上的速度差別
如下是AC代碼(ST表和線段樹都有 線段樹的並不是AC代碼)
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<ctype.h> using namespace std; int a[100001]; int st[100100][21]; inline int read() { int s=1, w=0; char ch=getchar(); for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1; for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0'; return s*w; } inline void init(int n) { for(int i=1;i<=n;i++) { st[i][0]=a[i]; } for(int j=1;(1<<j)<=n;j++) { for(int i=1;i+(1<<j)-1<=n;i++) { st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]); } } } inline int search(int l,int r) { int k=(int)(log((double)(r-l+1))/log(2.0)); return max(st[l][k],st[r-(1<<k)+1][k]); } int main() { int n,m; n=read(); m=read(); for(int i=1;i<=n;i++) { a[i]=read(); } init(n); for(int i=1;i<=m;i++) { int l,r; l=read(); r=read(); printf("%d\n",search(l,r)); } return 0; } // //#include<iostream> //#include<cstdio> //#include<cstring> //#include<cmath> //#include<ctype.h> //#define int long long int // //using namespace std; // //struct node //{ // int l; // int r; // int w; // int lazy; // int mxx; //}tree[5000010]; // //int n,m; //int ans; //int x,y; // //inline int read() //{ // int s=1, w=0; char ch=getchar(); // for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1; // for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0'; // return s*w; //} // //void build(int l,int r,int k) //{ // tree[k].l=l; // tree[k].r=r; // if(tree[k].l==tree[k].r) // { // tree[k].w=read(); // tree[k].mxx=tree[k].w; // return; // } // int m=(tree[k].l+tree[k].r)/2; // build(l,m,k*2); // build(m+1,r,k*2+1); // tree[k].w=tree[k*2].w+tree[k*2+1].w; // tree[k].mxx=max(tree[k*2].mxx,tree[k*2+1].mxx); //} // //inline void down(int k) //{ // tree[k*2].lazy+=tree[k].lazy; // tree[k*2+1].lazy+=tree[k].lazy; // tree[k*2].w+=tree[k].lazy*(tree[k*2].r-tree[k*2].l+1); // tree[k*2+1].w+=tree[k].lazy*(tree[k*2+1].r-tree[k*2+1].l+1); // tree[k].lazy=0; //} // //inline void ask_max_query(int k) //{ // if(tree[k].l>=x&tree[k].r<=y) // { // ans=max(ans,tree[k].mxx); // return; // } // if(tree[k].lazy) // { // down(k); // } // int m=(tree[k].l+tree[k].r)/2; // if(x<=m) // { // ask_max_query(k*2); // } // if(y>m) // { // ask_max_query(k*2+1); // } //} // //signed main() //{ // n=read(); // m=read(); // build(1,n,1); // for(int i=1;i<=m;i++) // { // x=read(); // y=read(); // ans=0; // ask_max_query(1); // cout<<ans<<endl; // } // return 0; //}