1. 程式人生 > >ST算法

ST算法

mil das get 浪費 左右 mini 技術 實現 strong

  ST算法用於解決RMQ(Range Minimum/Maximum Query)的問題。解決RMQ有三種實現的方法: 1.基於分治的樹狀數組 2.基於分治的線段樹 3.動態規劃下的ST表算法。點這裏查看它們的復雜度和區別。ST算法無法修改、O(1)的查詢、O(nlogn)的預處理;

  分析數組a的區間最小值。約定數組 a1~10 :4 7 9 6 3 2 8 5 1 6

O( nlogn )的預處理

  用 dp[ i ] [ j ] 表示 從 i 位置開始,長度為 2 ^ j (下文也稱步長)這一段的最小值。dp [ 3 ] [ 2 ]=min(a3~a3+4-1),dp [ 2 ] [ 3 ]=min(a2

~a2+8-1)。如 dp[ 4 ] [ 2 ] 表示從 4 位置(即數字 6),長度為 4(2 ^ 2)的最小值,即 6 3 2 8 的最小值,顯然 dp[ 4 ] [ 2 ] =2;

  求dp[ ] [ ] 數組的過程是:

  先求 dp[ i ] [ 0 ] ,再求 dp [ i ] [ 1 ],再求 dp [ i ] [ 2 ],再求 dp [ i ] [ 3 ] …… 這裏的 i 定會滿足 i+2 ^ j -1 <= 10(數組長度)【1】,當然 dp[ i ] [ 0 ]=a i

  對於 dp [ 1 ] [ 2 ] ,該如何求?由於長度2 ^ j

(j>1)總是可 一半一半 的,所以對於步長2 ^ j 的一段來說,我們分兩等長段,兩段中各自最小值的較小者,就是整段的最小值

  不難證明 dp [ 1 ] [ 2 ] = min(dp [ 1 ] [ 1 ],dp [ 1+2 ] [ 1 ] )

    即:min(a1~a4)=min( min(a1~a2),min(a3~a4))

  推到一般情況,再巧用位運算(和乘除運算同級,寫的時候要格外註意) dp [ i ] [ j ] = min(dp [ i ] [ 1<<(j-1) ],dp [ i+1<<(j-1) ] [ 1<<(j-1) ] )

  前面說到 先求 dp [ i ] [ 1 ],再求 dp [ i ] [ 2 ],再求 dp [ i ] [ 3 ]……因此 每一個 i 循環結束,才會進行 j 循環。從式子也能看出,正是得到了 每個 dp[ i ] [ j-1 ],我們才能得到 dp [ i ] [ j ]。因此 j 循環是外層循環,i 是內層。

技術分享圖片
void pre_set()
{
    for(int i=1;i<=n;i++)
        dp[i][0]=a[i];
    for(int j=1;j<20;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
        }
    }
}
pre_set()

O(1)的查詢

  ST算法不能修改,但有O(1)的查詢。現有查詢 query( l , r ) (區間長度 r - l + 1 ),如何使用dp [ ] [ ] 數組,快速得到答案?——計算這樣的 k ,2 ^ k= ( r - l + 1 ),如果 k 剛好為整數,那麽dp [ l ] [ k ] 就是答案,比如 query( 3 , 6 ),則 k = 2,dp [ 3 ] [ 2 ] =min(a3~a3+4-1)= 2 就是答案;可數據隨機的話,肯定大部分的 k 都不是整數,那麽就沒有 dp [ l ] [ x ] 剛好對應 r - l + 1 這個長度,但是可以從區間左右端點開始找相同步長的兩個 dp 值,比如 query ( 2 , 10 ) = min( a2 ~ a10 ),找不到整數 k 滿足那個式子,但是 min( a2 ~ a10 )= min( min( a2 ~ a9 ),min( a3 ~ a10 )),這樣兩段都有確切的 dp 值了。此時的步長需要非整數 k 向下取整(這兩段是不能隔斷的,k = floor ( log2( r - l + 1) ,),因為滿足 2 ^ k > ( r - l + 1 ) 的 k ,dp [ l ] [ k ] 的值牽扯到 [ l , r ] 之外的 a 值,是不可能的。當 k 為整數時,分的兩段是相同的,不影響結果,可一並考慮。

技術分享圖片
void query(int l,int r)
{
    int k=0;
    while(1<<(k+1)<=r-l+1)
        k++;
    return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
query()

  當數據規模非常大時,每次這樣找 k 是不行的,浪費很多時間,所以就可以預處理出每個 k ,代碼如下:

技術分享圖片
void lg_init()
{
    lg[0]=-1;
    for(int i=1;i<M;i++)
        lg[i]=lg[i>>1]+1;
}
lg_init()

最後,整個算法

技術分享圖片
const int M=1e5+5;

int n;
int a[n];
int lg[M];
int dp[M][20];

void pre_set()
{
    for(int i=1;i<=n;i++)
        dp[i][0]=a[i];
    for(int j=1;j<20;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
        }
    }
}

void lg_init()
{
    lg[0]=-1;
    for(int i=1;i<M;i++)
        lg[i]=lg[i>>1]+1;
}

void query(int l,int r)
{
    int k=lg[r-l+1];
    return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
RMQ

ST算法