一、ST表 1.预处理 f [ i ] [ j ] f[i][j] f [ i ] [ j ] 表示从i开始的长度为2j的区间( ( ( 即区间[ i , i + 2 j − 1 ] ) [i,i+2j−1]) [ i , i + 2 j − 1 ] )
递推公式( j (j ( j 在外层递增): ): ) :
f [ i ] [ j ] = m a x f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] f[i][j]=max{f[i][j−1],f[i+2j−1][j−1]} f [ i ] [ j ] = m a x f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ]
即将区间[ l , r ] [l,r] [ l , r ] 分为两个区间合并
2.查询 分为两段,第一段为区间[ l , 2 k ] [l,2k] [ l , 2 k ] ,第二段为区间[ r − 2 k + 1 , r ] [r−2k+1,r] [ r − 2 k + 1 , r ] ,其中k k k 为满足2 k ≤ r − l + 1 2k≤r−l+1 2 k ≤ r − l + 1 的所有数中最大的那个数
即区间[ l , r ] [l,r] [ l , r ] 的最大值为m a x f [ l ] [ k ] , f [ r − 2 k + 1 ] [ k ] max{f[l][k],f[r−2k+1][k]} m a x f [ l ] [ k ] , f [ r − 2 k + 1 ] [ k ]
3.例子 忠诚 洛谷 P1816
多次查询区间最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <cstdio> #include <algorithm> #include <cstring> using namespace std;int m,n;#define MAXN 100001 #define LOG 18 int st[MAXN][LOG];int lg2[MAXN];int main () { scanf ("%d %d" , &n, &m); memset (st, 0x3f , sizeof st); for (int i=1 ;i<=n;++i) scanf ("%d" , &st[i][0 ]); for (int len=1 ;len<LOG;++len) for (int i=1 ;i+(1 <<len)-1 <=n;++i) st[i][len]=min (st[i][len-1 ], st[i+(1 <<(len-1 ))][len-1 ]); for (int i=2 ;i<=n;++i) lg2[i]=lg2[i>>1 ]+1 ; while (m--){ int l,r;scanf ("%d %d" , &l, &r); int sz=lg2[r-l+1 ]; printf ("%d " , min (st[l][sz], st[r+1 -(1 <<sz)][sz])); } return 0 ; }
二、树上倍增 1.求解LCA (1) 预处理 首先结合dfs预处理出f [ i ] [ j ] , f [ i ] [ j ] f[i][j],f[i][j] f [ i ] [ j ] , f [ i ] [ j ] 表示节点i向上跳2j层的节点
递推公式:
f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] f[i][j]=f[f[i][j−1]][j−1] f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ]
即节点i分两次向上跳,每次跳2 j − 1 2^{j−1} 2 j − 1 层跳到的节点就是节点i i i 向上跳2 j 2^j 2 j 层的节点( 2 j − 1 × 2 = 2 j ) (2^{j−1}×2=2^j) ( 2 j − 1 × 2 = 2 j )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void load (int x, int fa) { f[x][0 ]=fa; dep[x]=dep[fa]+1 ; for (int i=1 ;i<20 ;++i) f[x][i]=f[f[x][i-1 ]][i-1 ]; } void dfs (int u, int fa) { load (u, fa); for (int i=head[u];i;i=nxt[i]){ int v=vv[i]; if (v==fa) continue ; dfs (v, u); } }
同时也可以像下面预处理出l o g 2 n log^n_2 l o g 2 n 的所有值以优化常数
1 2 for (int i=2 ;i<=tot;++i) lg2[i]=lg2[i>>1 ]+1 ;
(2) 查询 首先使两个查询节点跳至同一高度后(因为它们的最近公共祖先不可能低于这两点,跳跃方法同下),当前层记为x x x ,然后从l o g 2 x log^x_2 l o g 2 x 到0 0 0 枚举(递减能保证可以完全分解成二进制)j j j ,如果上跳2 j 2^j 2 j 层后不重合,那么就继续跳,重合则不跳,使两点层数一直逼近最近公共祖先,最后跳完2 0 2^0 2 0 层后,两点必定会停在最近公共祖先的下一层,所以最后直接取当前层i i i 的f [ i ] [ 0 ] f[i][0] f [ i ] [ 0 ] 就好了。
其中,可以直接从最大可能的i i i 开始枚举,因为反正i i i 也很小。
1 2 3 4 5 6 7 8 9 10 11 inline int lca (int a, int b) { if (dep[a]<dep[b]) swap (a, b); for (int i=20 ;i>=0 ;--i) if (dep[f[a][i]]>=dep[b]) a=f[a][i]; if (a==b) return a; for (int i=20 ;i>=0 ;--i) if (f[a][i]!=f[b][i]) a=f[a][i], b=f[b][i]; return f[a][0 ]; }
这是一种在线求LCA的算法,其实还有Tarjan这种效率高的离线算法。
2.O ( 1 ) O(1) O ( 1 ) 求LCA 另外还有一种n l o g n nlogn n l o g n 预处理,每次O ( 1 ) O(1) O ( 1 ) 查询的方法。
即用欧拉序+ R M Q +RMQ + R M Q 可实现O ( 1 ) O(1) O ( 1 ) 求得L C A LCA L C A 。先d f s dfs d f s 全树,记录欧拉序(就是记下d f s dfs d f s 走过的节点),然后在欧拉序上以节点深度作为权值建S T ST S T 表,每次查欧拉序上深度最小的点即为L C A LCA L C A 。此时将问题转换为区间求最小值R M Q RMQ R M Q 问题。
(1)预处理 1 2 3 4 5 6 7 8 9 10 11 void dfs (int u, int fa) { dfn[u]=++tot; f[tot][0 ]=u; dep[u]=dep[fa]+1 ; for (int i=head[u];i;i=nxt[i]){ int v=vv[i]; if (v==fa) continue ; dfs (v, u); f[++tot][0 ]=u; } }
1 2 3 4 5 6 7 8 for (int i=1 ;i<20 ;++i) for (int j=1 ;j+(1 <<i)-1 <=tot;++j) if (dep[f[j][i-1 ]]<dep[f[j+(1 <<(i-1 ))][i-1 ]]) f[j][i]=f[j][i-1 ]; else f[j][i]=f[j+(1 <<(i-1 ))][i-1 ]; for (int i=2 ;i<=tot;++i) lg2[i]=lg2[i>>1 ]+1 ;
(2)查询 1 2 3 4 5 6 7 8 int lca (int a, int b) { int l=dfn[a],r=dfn[b]; if (l>r) swap (l,r); int lg=lg2[r-l+1 ]; if (dep[f[l][lg]]<dep[f[r-(1 <<lg)+1 ][lg]]) return f[l][lg]; else return f[r-(1 <<lg)+1 ][lg]; }
3.求路径上最值 求树上两点a , b a,b a , b 路径上的最小权值
我们再设一个g [ i ] [ j ] g[i][j] g [ i ] [ j ] 表示节点i i i 向上跳2 j 2^j 2 j 内所经过的最小权值即可,转移方程:
g [ i ] [ j ] = m i n ( g [ i ] [ j − 1 ] , g [ f [ i ] [ j − 1 ] ] [ j − 1 ] ) g[i][j]=min(g[i][j−1],g[f[i][j−1]][j−1]) g [ i ] [ j ] = m i n ( g [ i ] [ j − 1 ] , g [ f [ i ] [ j − 1 ] ] [ j − 1 ] )