倍增 ST表
倍增
倍增是與二分相反的算法,其核心思想是每次擴大一倍,以 2 n 2^n 2n的速度極大擴展空間
-
原理:任意整數均可被分解為若干個以2為底的冪項和。最經典的倍增是 2 i 2^i 2i(實際應為 e i e^i ei)
-
流程:定義倍增表 g [ i ] [ j ] g[i][j] g[i][j],其中 i i i代表起始元素索引, j j j代表倍增組數(該組對應區間長度(包含原元素本身)為 2 j 2^j 2j,該元素在該組中倍增后的位置為 i + 2 j ? 1 i+2^j-1 i+2j?1)。 n n n個元素最多可倍增 log ? 2 n \log_2n log2?n組(經驗值在20左右)。
-
倍增遞推式: g [ i ] [ j ] = g [ g [ i ] [ j ? 1 ] ] [ j ? 1 ] g[i][j]=g[g[i][j-1]][j-1] g[i][j]=g[g[i][j?1]][j?1]
-
含義:第 i i i個元素倍增 j j j組,等于其倍增 j ? 1 j-1 j?1組后再倍增 j ? 1 j-1 j?1組,即 i + 2 j = ( i + 2 j ? 1 ) + 2 j ? 1 i+2^j=(i+2^{j-1})+2^{j-1} i+2j=(i+2j?1)+2j?1。特別地,當 j = 0 j=0 j=0時,表示其倍增 0 0 0組,倍增區間長度為 2 0 2^0 20,即為 i i i本身

void init(){
for(int i=0;i<n;i++) g[i][0]=i;
for(int j=1;j<=log2(n);j++)//倍增輪數,或寫成(1<<j)<=n
for(int i=0;i<n;i++)//逐步處理每組組內倍增
g[i][j]=g[g[i][j-1]][j-1];
}
- 優化:打表計算所有用到的 log ? 2 \log_2 log2?
extern vector<int>l2(n+1);
void calc(){
l2[0]=-1;
for(int i=1;i<=n;i++) l2[i]=l2[i>>1]+1;
}
倍增法具有極其廣泛的應用,可用于求解ST表、LCA等問題。
ST表
可重復貢獻性問題:對于運算 opt \texttt{opt} opt,若滿足 x opt x = x x\ \texttt{opt}\ x\ =\ x x opt x = x,即重復該運算對答案無影響,則 opt \texttt{opt} opt稱為可重復貢獻性運算,其對應的區間詢問就是一個可重復貢獻問題。常見的 opt = max/min , gcd \texttt{opt}={\texttt{max/min},\texttt{gcd}} opt=max/min,gcd等,但 opt ≠ ∑ , ∏ \texttt{opt}\ne \sum,\prod opt=∑,∏等。
可重復貢獻性常用于兩個交集 ≠ ? \ne \varnothing =?的區間合并。若大區間能被 n n n個小區間所覆蓋,則 opt \texttt{opt} opt(大區間)= opt \texttt{opt} opt(小區間1, ? \cdots ? ,小區間 n n n)。
ST表用于高效解決具有可重復貢獻性問題的數據結構,其基于倍增的思想。
ST表求解靜態區間RMQ問題
問題:給定長度為 n n n的靜態序列(無任何修改操作),有 m m m次詢問,每次給定 L , R L,R L,R,查詢序列區間 [ L , R ] [L,R] [L,R]內的最值,必須以 O ( 1 ) O(1) O(1)復雜度回答每次詢問。

初始化ST表 O ( n log ? 2 n ) O(n\log_2n) O(nlog2?n)
流程:定義 d p [ s ] [ k ] dp[s][k] dp[s][k]表示區間 [ s , s + 2 k ) [s,s+2^k) [s,s+2k)內的最值,根據鴿巢原理, [ s , s + 2 k ) [s,s+2^k) [s,s+2k)的區間最值必定為 最值 ( 最值( 最值(區間 [ s , s + 2 k ? 1 ) [s,s+2^{k-1}) [s,s+2k?1)內的最值和區間 [ s + 2 k ? 1 , s + 2 k ) [s+2^{k-1},s+2^k) [s+2k?1,s+2k)內的最值 ) ) )。
狀態轉移方程式: d p [ s ] [ k ] = R M Q ( d p [ s ] [ k ? 1 ] , d p [ s + ( 1 ? ( k ? 1 ) ) ] [ k ? 1 ] ) dp[s][k]=RMQ(dp[s][k-1],dp[s+(1\ll(k-1))][k-1]) dp[s][k]=RMQ(dp[s][k?1],dp[s+(1?(k?1))][k?1])
extern int dp[MAX][log2(n)+10];
int opt(int,int);
void init(){
for(int i=0;i<n;i++) dp[i][0]=a[i];
for(int j=1;j<=log2(n);j++)
for(int i=0;i+(1<<j)-1<n;i++)
dp[i][j]=opt(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
查詢任意區間最值 O ( 1 ) O(1) O(1)
原理:設序列區間 [ L , R ] [L,R] [L,R],給定查詢區間 [ l , r ] [l,r] [l,r],區間長度為 l e n = r ? l + 1 len=r-l+1 len=r?l+1。根據鴿巢原理,將查詢區間分為 [ l , r ′ ] [l,r'] [l,r′]和 [ l ′ , r ] [l',r] [l′,r]兩個等長小區間,其長度均為 x x x( x ≤ l e n x\le len x≤len且 2 x ≥ l e n 2x\ge len 2x≥len),確保兩個小區間都能完全覆蓋,且對于 d p [ ] [ k ] dp[][k] dp[][k],需確保 2 k = x 2^k=x 2k=x, k = log ? 2 ( l e n ) k=\log_2(len) k=log2?(len),由此可知 r ’ = l + 2 k ? 1 r’=l+2^k-1 r’=l+2k?1, l ′ = r ? 2 k + 1 l'=r-2^k+1 l′=r?2k+1。
狀態轉移方程式: R M Q ( d p [ l ] [ k ] , d p [ r ? ( 1 ? k ) + 1 ] [ k ] ) RMQ(dp[l][k],dp[r-(1\ll k)+1][k]) RMQ(dp[l][k],dp[r?(1?k)+1][k])
extern int dp[MAX][log2(n)+10],l2[n];
extern int l,r;
int opt(int,int);
int query(){
int k=l2[r-l+1];
return opt(dp[l][k],dp[r-(1<<k)+1][k]);
}

浙公網安備 33010602011771號