LCA學習筆記
定義:在一棵樹中,兩個節點的最近公共祖先。
1.暴力求法
處理出每個點的深度,先把深度較深的一個點沿著父節點方向一直走到與另一個點相同的深度,如果此時兩個點不同,那麼兩個點一起向上跳(程式碼實現過於簡單,這裡不過多贅述)
2.倍增最佳化暴力
注意到我們在暴力求法中,點是一步一步向上跳的,那我們有沒有辦法加快這個過程呢?這個時候就要使用倍增了,預處理出每個節點的深度dep,和它的 2^i 級祖先,那麼我們可以在跳的時候一次進行多步跳躍
預處理程式碼(也可以寫在一起)
void dfs(int u,int fa){
dep[u]=dep[fa]+1,f[u][0]=fa;
for(int i=head[u];i;i=ne[i]){
int v=to[i];
if(v==fa) continue ;
dfs(v,u);
}
}
void init(){
for(int j=1;j<=20;++j)
for(int i=1;i<=n;++i)
f[i][j]=f[f[i][j-1]][j-1];
}
查詢LCA:
這個要注意:在列舉祖先時一定要倒序列舉i,因為要從里根近的一步一步走到深度更大的,剩下的根據暴力的思想不難寫出:
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;--i){
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
}
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
3.RMQ
首先把這棵樹轉成尤拉序列,用pos[i]記錄樹上i點第一次出現在尤拉序列中的位置,由於尤拉序列的性質,兩個點的lca一定時兩個點在序列中出現的區間中dep最小的點所對應的點,(可以結合尤拉序列理性感受一下),那麼樹上求lca問題就可以轉化成序列RMQ問題了
初始化:
int pos[N];//樹上節點在序列第一次出現中的編號
int a[N];//尤拉序列
int dep[N];
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
pos[u]=++tot;
a[++tot]=dep[u];
for(int i=head[u];i;i=ne[i]){
int v=to[i];
if(v==fa) continue ;
dfs(v,u);
}
a[++tot]=dep[u];
}
查詢
(靜態區間最小值,不過多贅述)
4.樹鏈剖分
初始化
常規進行重鏈剖分或者長鏈剖分(只求lca的話建議長鏈剖分),不貼程式碼
查詢lca
根據樹剖已經拆樹成鏈,也和倍增類似可以一次跳多個
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]])
x=f[top[x]];
else
y=f[top[y]];
}
if(dep[u]>dep[v]) return v;
return u;
}