ST表學習筆記

通過了樹狀數組的折磨學習,小蒟蒻\(hqk\)又學了一個新的結構——\(ST\)表(其實應該是\(ST\)算法,由於\(T\)原本就有\(table\)的意思,可是因爲你們都這麼叫,接下來的文章裏也沿用這一說法。算法

\(ST\)

\(1\))區間\(RMQ\)問題

區間\(RMQ\)($ Range\space Maximum/minimum\space Query \()問題,顧名思義,就是詢問某個區間的最大最小值。這種問題一般有不少種解法,好比線段樹、樹狀數組,還有像笛卡爾樹、莫隊這樣的神仙作法。可是咱們今天要探討的解法是一種比較好理解的算法——\)ST$表數組


\(2\))啥是\(ST\)

\(ST\)表是基於動態規劃的一種算法。爲啥叫表呢?是由於\(ST\)表在運行過程當中是先進行預處理,而後進行查詢。這種算法可以實現\(O(nlogn)\)預處理,\(O(1)\)查詢。是一種比較高效的算法,可是須要注意的一點是,這種算法的空間複雜度較高,須要一些優化(或者使用其餘的算法來代替)才能經過一些毒瘤題目函數


\(3\)\(ST\)表的基本思想

其實,\(ST\)表的基本思想就是\(dp\)學習

咱們用\(a[1···n]\)表示一組數。設\(f[i][j]\)表示從\(a[i]\)加到\(a[i+2^i-1]\)這個範圍內的最大值,也就是說\(f[i][j]\)表示以\(a[i]\)爲起點連續\(2^i\)個數的最大值。因爲元素個數爲\(2^j\)個,因此咱們能夠考慮分治的思想,分而治之,分別求出左半邊(\(2^{j-1}\))的最大值,再求右半邊的最大值,即\(f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1])\)從前日後掃描一下就能夠預處理出來。大數據

接下來咱們要考慮如何進行查詢優化

每提問一個區間\([l,r]\),必定會存在一個數\(x\),使得\(2^x\leq r-l+1\)。只要求出了這個值,咱們就能夠用已經與處理完畢的\(f[][]\)來進行回答了。spa

具體方法是:\(min(f[l][x],f[r-2^x+1][x])\)這個東西能夠再\(O(1)\)的時間內求出來code

等等,怎麼求\(x\)呢?**get

其實,求\(x\)的方法也很簡單,就是\(log_2^{r-l+1}\),具體是爲何須要讀者本身去思考,這裏就再也不贅述了

可是怎麼求\(log_2^{r-l+1}\)呢?

咱們能夠維護一個\(log[]\),其中\(log[i]\)表示\(log_2^i\)。至於\(log[i]\)的計算,咱們能夠用下面一個遞推式:\(log[i]=log[i/2]+1\);不過若是再懶一點的話能夠調用\(cmath\)庫裏的\(log2\)函數


\(4\)\(ST\)表的例題

其實就是\(ST\)表的實現

例題(\(1\)

P3865 【模板】ST表

題目背景

這是一道\(ST\)表經典題——靜態區間最大值

請注意最大數據時限只有\(0.8s\),數據強度不低,請務必保證你的每次查詢複雜度爲$ O(1)$

題目描述

給定一個長度爲$ N \(的數列,和\) M $次詢問,求出每一次詢問的區間內數字的最大值。

輸入輸出格式

輸入格式:

第一行包含兩個整數$ N, M$,分別表示數列的長度和詢問的個數。

第二行包含\(N\)個整數(記爲 \(a_i\)),依次表示數列的第$ i$項。

接下來$ M$行,每行包含兩個整數 \(l_i, r_i\),表示查詢的區間爲$ l_i, r_i$

輸出格式:

輸出包含$ M$行,每行一個整數,依次表示每一次詢問的結果。

題解

這就是一道板子題啊!莫慌莫慌,都在代碼裏了——

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=1e6+5,logn=20;

int log[N],f[N][logn+5],a[N];
int n,m,x,y;

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    log[0]=-1;//必定要注意這個小細節,才能使log[1]=0
    for(int i=1;i<=n;++i)
    {
        f[i][0]=a[i];//將形如[i,i]的都標做a[i],做爲dp的邊界條件
        log[i]=log[i>>1]+1;//對log的處理
    }
    for(int j=1;j<=logn;++j)//外循環是1~logn
        for(int i=1;i+(1<<j)-1<=n;++i)//內循環是一直到出界
            f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);//套進剛纔的式子中
    while(m--)//循環讀入數據
    {
        scanf("%d%d",&x,&y);
        int s=log[y-x+1];
        printf("%d\n",max(f[x][s],f[y-(1<<s)+1][s]));//這些式子咱們都進行過說明,這裏就不說了
    }
    return 0;
}

例題(\(2\)

P2251 質量檢測

題目背景

題目描述

爲了檢測生產流水線上總共\(N\)件產品的質量,咱們首先給每一件產品打一個分數\(A_i\)表示其品質,而後統計前M件產品中質量最差的產品的分值\(Q_m = min(A_1, A_2, ... A_m)\),以及第\(2\)至第\(M + 1\)件的\(Q_{m + 1}, Q_{m + 2} ...\) 最後統計第\(N - M + 1\)至第\(N\)件的\(Q_n\)。根據\(Q\)再作進一步評估。

請你儘快求出\(Q\)序列。

輸入輸出格式

輸入格式:

輸入共兩行。

第一行共兩個數\(N、M\),由空格隔開。含義如前述。

第二行共\(N\)個數,表示\(N\)件產品的質量。

輸出格式:

輸出共\(N - M + 1\)行。

\(1\)\(N - M + 1\)行每行一個數,第\(i\)行的數\(Q_{i + M - 1}\)。含義如前述。

題解

其實這道題也算一道模板題,只不過是將詢問變成了讓你本身循環跑一邊,不過要注意的是,必定要將\(m-1\)後再進行循環

代碼實現:

#include<cstdio>
#include<iostream>

using namespace std;

const int maxn=2000010;
const int logn=20;

int log[maxn],f[maxn][logn],a[maxn];
int n,m;

void init()
{
    scanf("%d%d",&n,&m);
    log[0]=-1;
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        f[i][0]=a[i];
        log[i]=log[i>>1]+1;
    }
    for(int j=1;j<=logn;++j)
        for(int i=1;i+(1<<j)-1<=n;++i)
            f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
}

void work()
{
    m-=1;
    for(int i=1;i+m<=n;++i)
    {
        int s=log[m+1];
        printf("%d\n",min(f[i][s],f[i+m-(1<<s)+1][s]));
    }
}

int main()
{
    init();
    work();
    return 0;
}
相關文章
相關標籤/搜索