今天打了人生第一道ST表題(其實只是ST表跑得最快);
ST表是一種用來解決RMQ問題的利器。。。
大體操作有兩步:
第一部分nlogn預處理
第二部分O(1)詢問
預處理就是運用倍增+區間動規
ST表使用DP思想求解區間最值,貌似屬於區間動態規划,不過區間在增加時,每次並不是增加一個長度,而是使用倍增的思想,每次增加2^i個長度。
使用F[i,j]表示以i為起點,區間長度為2^j的區間最值,此時區間為[i,i + 2^j - 1]。
比如,F[0,2]表示區間[0,3]的最值,F[2,2]表示區間[2,5]的最值。
在求解F[i,j]時,ST算法是先對長度為2^j的區間[i,i + 2^j - 1]分成兩等份,每份長度均為2^(j - 1)。之后在分別求解這兩個區間的最值F[i,j - 1]和F[i + 2^(j - 1),j - 1]。,最后在結合這兩個區間的最值,求出整個區間的最值。
狀態轉移方程是 F[i,j] = min(F[i,j - 1],F[i + 2^(j - 1),j - 1])
初始狀態為:F[i,0] = A[i]。
代碼如下:
1 void makeST() 2 { 3 pre[0]=1;for(int i=1;i<=20;i++) pre[i]=pre[i-1]<<1; 4 pre2[0]=-1;for(int i=1;i<=n;i++) pre2[i]=pre2[i>>1]+1; 5 for(int i=1;i<=n;i++) ST[i][0]=i; 6 for(int j=1;j<=20;j++) 7 for(int i=1;i<=n;i++){ 8 if(i+pre[j]-1<=n){ 9 int x1=ST[i][j-1],x2=ST[i+pre[j-1]][j-1]; 10 if(a[x1]>a[x2]) ST[i][j]=x1; 11 else ST[i][j]=x2; 12 } 13 } 14 }
一開始打錯了好多個地方!!!醉了!!!
1.要把20的循環放在外面,手賤打錯了。。。
2.pre2數組是用來支持詢問的。。。相當於是logi,所以是for(1--n);
詢問操作:
在預處理期間,每一個狀態對應的區間長度都為2^i。由於給出的待查詢區間長度不一定恰好為2^i,因此我們應對待查詢的區間進行處理。
這里我們把待查詢的區間分成兩個小區間,這兩個小區間滿足兩個條件:(1)這兩個小區間要能覆蓋整個區間(2)為了利用預處理的結果,要求小區間長度相等且都為2^i。注意兩個小區間可能重疊。
在程序計算求解區間長度時,並沒有那么麻煩,我們可以直接得到i,即等於直接對區間長度取以2為底的對數。這里,對於區間[3,11],其分解的區間長度為int(log(11 - 3 + 1)) = 3,這里log是以2為底的。
根據上述思想,可以把待查詢區間[x,y]分成兩個小區間[x,x + 2^i - 1] 和 [y - 2^i + 1,y] ,其又分別對應着F[x,i]和F[y - 2^i + 1,i],此時為了求解整個區間的最小值,我們只需求這兩個值得最小值即可,此時復雜度是O(1)。
注意細節要加1。。。
代碼實現如下:
1 int query(int l,int r) 2 { 3 if(l==r)return l; 4 int x=pre2[r-l+1]; 5 int x1=ST[l][x],x2=ST[r-pre[x]+1][x]; 6 return a[x1]>a[x2]?x1:x2; 7 }