1. 程式人生 > 實用技巧 >淺談ST表

淺談ST表

原題是P3865

傳送門

ST表是一種資料結構

ST表類似於樹狀陣列和線段數的資料結構,能夠快速訪問一定區間內的最大值/最小值的值(解決RMQ問題:Range Minimum/Maximum Query,即區間最值查詢)問題的離線演算法。

其預處理的時間複雜度與線段樹,樹狀陣列相同,為O(n*logn)。

但ST表查詢的時間複雜度為O(1),而線段樹和樹狀陣列查詢的時間複雜度都為O(logn)。是不是更加優秀了呢!

首先我們來表示一下(以儲存最大值為例):

void do_it(){
    for(int i=1;i<=n;i++) st[i][0]=a[i];
    for(int i=1
;(1<<i)<=n;i++){ for(int j=1;j+(1<<i)-1<=n;j++){ st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]); } } return ; }

首先我們需要說明一下,st[i][j]的意義表示從i開始向右數pow(2,j)位中的最大值

由於我們在內層迴圈中有判斷j+(1<<i)-1<=n條件,所以一定會有i+pow(2,j)-1<=n

所以我們很容易得到st[i][j]一定是合法的範圍內的

然後我們需要證明一個很簡單的定理:

當我們將log2x向下取整時,有2log2x >x/2    ①

來證明一下:

因為我們將log2x向下取整,假定x==pow(2,n),則log2x==n

所以我們有當pow(2,n)<= x < pow(2,n+1)時的log2x均為n

所以就有2log2x == pow(2,n),因為易得有pow(2,n)>pow(2,n+1)/2

因為x<pow(2,n+1)

所以有2log2x > x/2

QED.

然後我們現在還要考慮如何求得向下取整的log2x 這一問題

1.通過對數運演算法則logab==logxb/logxa,以及呼叫cmath庫中的log函式實現

x=log(b)/log(a)
(x是我們要求的logab)

優點:方便

缺點:當查詢的次數很多時,很有可能使得時間複雜度大大增加

2.預處理得到範圍內的所有logn

優點:訪問方便且訪問的時間複雜度為O(1)

缺點:訪問次數很少是預處理會增加很大的時間複雜度

但是我們知道預處理的時間複雜度其實也是O(1)的,只不過只是一個很大的常數罷了,只要在範圍內就不會TLE

那麼我們如何範圍內一定範圍內的最大值呢?

因為我們已經有定理①,所以當我們要求i到i+pow(2,j)-1範圍內的最大值時,我們只需要求i到i+pow(2,j-1)的最大值和(i+pow(2,j)-1)-pow(2,j-1)+1到i+pow(2,j)-1範圍內的最大值並且取兩者中德較大值就可以了!

AC程式碼:

#include<cctype>
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 100010;
int lg[N];
int n,m;
int a[N];
int st[N][20];
int l,r;
int read()
{
    int x = 1,a = 0;
    char ch = getchar();
    while(ch < '0' || ch > '9'){//如果讀進來的不是數字……
        if(ch == '-')x = -1;//判斷負號
        ch = getchar();
    }
    while(ch <= '9'&&ch >= '0'){//如果讀進來的是數字……
        a = a * 10 + ch - '0';
        ch = getchar();
    }
    return x*a;
}
void init(){
    lg[1]=0;
    for(int i=2;i<=n;i++){
        lg[i]=lg[i/2]+1;
    }
    return ;
}
void do_it(){
    for(int i=1;i<=n;i++) st[i][0]=a[i];
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
            st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
        }
    }
    return ;
}
int main(){
    n=read();
    m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    init();
    do_it();
    for(int i=1;i<=m;i++){
        l=read();
        r=read();
        printf("%d\n",max(st[l][lg[r-l+1]],st[r-(1<<lg[r-l+1])+1][lg[r-l+1]]));
    }
    return 0;
}