1. 程式人生 > >ST表 「 從入門到入門 · 淺顯理解 」

ST表 「 從入門到入門 · 淺顯理解 」

ST 表是個好東西,雖然前些天 ldq 學長已經講完啦,但是那天他講了那麼多,讓智商受限的我完全沒有全部接受,選擇性的扔掉了一部分(其實不捨的扔,記不住QAQ)。

ST 表最簡單的應用就是查詢區間最大值(或著最小值,這裡以最大值為例),它(單純 ST 表自己)需要你先修改之後(如果有修改要求),得到一個確切陣列之後,經過 O ( nlogn ) 的預處理,然後就可以做到 O ( 1 ) 查詢啦。


ST 表的預處理操作:

     對於一個有 n 個數的 a [ n ] ,如果需要用一個二維陣列 f [ n ] [ t ] ,其中 n 是指的用這 n 個數,t 是指的 n 最大是 2 的多少次冪,即 2 ^ t >= n 向上取整。

     陣列 f [ n ] [ t ] 是用來幹什麼的呢?f [ i ] [ j ] 是指以 a [ n ] 中第 i 個數開始,長度為 2 ^ j 的最大值,這樣就清楚了陣列 f [ n ] [ t ] 中 t 的來源啦吧。

所以我們要做的第一步預處理中:

f [ i ] [ 0 ] 就是存的 a [ i ] 值自己。

f [ i ] [ 1 ] 就是存的從 a [ i ] 開始往後一個,即 a [ i ] 和 a [ i + 1 ] 的最大值

……

以此類推,就可以得到我們想要的陣列 f [ n ] [ t ] 啦。

     那麼我們怎麼去實現這個東西呢?不能一個個的去列舉吧,那樣的話,還不如用線段樹(我是這麼想得H_H),當然啦,這個問題在 ST 表被想出來的時候就解決啦,那就是遞推得到,先看一下程式碼(不理解沒關係,慢慢看)。

int f[maxn][20];
int a[maxn];
int n;
void st()
{
    for(int i = 1; i <= n; i ++) f[i][0] = a[i]; 
    int t = log(n) / log(2) + 1;
    for(int j = 1; j < t; j ++)
    {
        for(int i = 1; i <= n - (1 << j) + 1; i ++)
        {
            f[i][j] = max(f[i][j-1],f[i + (1 << (j - 1))][j - 1]);
        }
    }
}

第一個 for 迴圈 應該沒什麼理解障礙,就是上面說的把自己存進去,因為是 2 ^ 0 。

第二個是兩個 for 迴圈,也就是遞推的部分,我們這樣子來實現:

     f [ i ] [ j ] 代表以 a [ i ] 開始長度為 2 ^ j 的數裡面的最大值,即 f [ i ] [ j ] = max ( a[ i ] , …… , a [ i + 2 ^ j ] )。

我們先看一個簡單的例子:現在一個數列是 a [ ]  = { 1 ,2,3,4,5} , 那麼 (下標)0 ~ 2的最大值 Max1 ,和 3 ~ 4 最大值 Max2,兩個裡面的最大值 Max 就是整個區間的最大值,這個很好理解吧!

那麼我們回到求 f [ i ] [ j ] 上,這個問題就變的和這個一樣子啦,不過就是需要改變一下長度,我們把這個長度 len = 2 ^ j 的區間分成長度分別為 len1 = 2 ^ ( j - 1 ) 和 len2 =  2 ^ ( j - 1 ) 的這兩個區間,只要求出來這兩個的最大值就可以像上面那樣子得到最終結果啦。

所以我們就可以理解上面我說的意思啦:f [ i ] [ j - 1] 代表從 a [ i ] 開始前 2 ^ ( j - 1 ) 個元素的最值,f [ i + ( 1 << ( j - 1 ) )] [ j - 1 ] 就是後面這 2 ^ ( j - 1 ) 個元素裡面的最值,這樣子合併取個最大值,就是總的最大啦,也就是程式碼中的:f [ i ] [ j ] = max ( f [ i ] [ j - 1 ] , f [ i + ( 1 << ( j - 1 ) ) ] [ j - 1 ] ) 。


這裡有幾點需要注意的(自己xj說的,不對可以告訴我哈):

第一點是大家都知道的,在預處理中控制第二維的迴圈,也就是 for ( int j = 1; j <= t; j ++) ,一般的話 t 取 20 左右就可以啦,你要是不放心就先計算一下再開陣列一樣的,這個一定要放在外面,因為我們要通過遞推得到,如果放在裡面的話,就得不到我們想要的結果啦(可以感覺一下子)。

第二點就是那個 f [ i ] [ j ] 這裡可能有理解誤區,就算是不夠 2 的整數次冪也沒有關係的,初始化或者邊界處理一下就可以啦,最大值最小值對應上相應的初始化。

第三點是對於 1  << ( j  - 1 )  這個東西,這和求那個 t 的時候的意思一個樣子啦,這樣子寫比較高大尚一些。

ST 表在預處理時採用倍增和DP思想。


ST 表查詢操作:

關於查詢操作,想一想怎麼樣子可以做到 O ( 1 ) 查詢的呢。

先來看簡單的栗子(簡單的看懂啦,難得就可以啦):

a [ ]  = { 1,2,4,5,6,3,6,8,7,0 },在這些數裡面我們想知道 1 ~ 8(下標從 0 開始) 的最大值。

如果我們已經用陣列 f [ n ] [ t ] 存好啦,我們可以先計算一下長度可以是 2 的多少次冪,左端點是 x = 1, 右端點是 y = 8,長度 m = log ( y - x  + 1) / log ( 2 ),我們需要差的就是從 a [ x ] 開始 m 這麼長的區間,由於 m 存在邊界問題,所以我們還需要查的區間就變成 [ x ] [ m ] ,但是這樣子查的話不免可能會漏掉東西而且預處理的結果我們也沒有用到,所以我們把這個 m 的長的區間分成啦兩個部分,也就可以利用預處理的結果啦。

所以區間就變成了 [ x ] [ x + 2 ^ m - 1 ] 和 [ y - 2 ^ m + 1] [ y ] ,這兩個區間分別對應的陣列 f [ n ] [ t ] 是 f [ x ] [ m ] 和 f [ y - ( 1 << m ) + 1 ] [ m ] (這個可能存在不是很好理解的問題,不過不要忘記啦 f [ i ] [ j ] 的意思,是指從 a [ i ] 開始的長度為 2 ^ j 的最值,這樣就可以啦,實在不好理解,可以手動畫一畫)。

程式碼:

int query(int x, int y)
{
    int t = log(abs(y-x + 1))/ log(2);
    int a = f[x][t];
    int b = f[y - (1 << t) + 1][t];
    return max(a,b);
}

 

完整的程式碼(其實僅僅是一點點,可以戳我):

const int maxn = 1000004;
int f[maxn][20];
int a[maxn];
int n;
void st()
{
    for(int i = 1; i <= n; i ++) f[i][0] = a[i]; 
    int t = log(n) / log(2) + 1;
    for(int j = 1; j < 20; j ++)
    {
        for(int i = 1; i <= n - (1 << j) + 1; i ++)
        {
            f[i][j] = max(f[i][j-1],f[i + (1 << (j - 1))][j - 1]);
        }
    }
}
int query(int x, int y)
{
    int t = log(abs(y-x + 1))/ log(2);
    int a = f[x][t];
    int b = f[y - (1 << t) + 1][t];
    return max(a,b);
}
--------------------- 
作者:Mercury_Lc 

就羅嗦這麼多啦,還有好多作業QAQ(看在我這麼慘的份上,要是轉載,別忘記啦我的地址QWQ)