兩種特別有用的求LCA的方法
阿新 • • 發佈:2018-11-02
第一種:樹鏈剖分@TOC
一:知識儲備
- 重節點:以i為根的節點中結點數最多的結點
- 輕結點:其他結點
- 重鏈:由重節點連成的鏈
二:實現必需品:
- dep存深度
- son存重節點是誰
- siz存以i為根的子樹大小
- fa存父親是誰
- top存“頭”
~重節點的頭是他所在鏈的第一個出現的點
~輕結點的頭是他自己
三:程式碼:
#include <iostream> #include <cstdio> #include <vector> using namespace std; const int maxn=100; //siz是它對應的結點的子樹個數,算上他自己 //son儲存這個點的重兒子 struct Edge{ int to;//下一個點 int val;//邊權 }; //這裡使用鄰接表存圖,和鏈式前向星一樣 vector<Edge>g[maxn]; int fa[maxn],siz[maxn],son[maxn],dep[maxn]; int top[maxn]; //求出每個點的重兒子 void dfs1(int u){ siz[u]=1; son[u]=0; for(int i=0;i<g[u].size();i++){//迴圈邊 Edge &v=g[u][i]; if(fa[u]==v.to)continue; dep[v.to]=dep[u]+1; fa[v.to]=u; dfs1(v.to); if(siz[son[u]]<siz[v.to])son[u]=v.to; siz[u]+=siz[v.to]; } } //若他在一條重鏈上,他的top就是第一個出現的點 //反之,他的top就是他自己 //求每個點的top void dfs2(int u,int tp){ top[u]=tp; if(son[u])dfs2(son[u],tp);//走重鏈 for(int i=0;i<g[u].size();i++){ Edge &v=g[u][i]; if(v.to==son[u]||v.to==fa[u])continue; //若是一個重兒子,因為已經走過就不走了 //若是父親當然也不走 dfs2(v.to,v.to); } } int lca(int u,int v){ int fu=top[u]; int fv=top[v]; while(fu!=fv){ if(dep[fu]<dep[fv]){ swap(fu,fv); swap(u,v); } //跳的是深度較深的 u=fa[fu]; fu=top[u]; } if(dep[u]<dep[v])swap(u,v); //返回的是深度小的那個 //因為他們這時的top相同,說明在一條重鏈上,深度較淺的那個是lca return v; } int main() { int n; scanf("%d",&n); for(int i=0;i<n-1;i++){ int u,v,val; scanf("%d%d%d",&u,&v,&val); g[u].push_back((Edge){v,val}); g[v].push_back((Edge){u,val}); } dep[1]=1; dfs1(1); cout<<"a"; dfs2(1,1); cout<<"b"; while(1){ int u,v; cin>>u>>v; cout<<lca(u,v); } return 0; }
第二種:倍增求LCA
一:實現必需品
- dp儲存這個點向上跳2的k次方步之後的點,這個點的祖先
- dep儲存點的深度
二:實現步驟
- 首先把深度預處理出來
- 把兩個點的深度跳到相同,再一起往上跳,直到跳到相同深度
- 求LCA
三:程式碼
#include <iostream> int dp[maxn][20]; int dep[maxn]; //首先把深度跳到相同,再一起往上跳 ,直到跳相同的深度 void dfs(int u,int fa,int h){ dep[u]=h; int t=0; while(dp[u][t]){ dp[u][t+1]=dp[dp[u][t]][t]; t++; } for(int i=0;i<g[u].size();i++){ int now=g[u][i]; if(now==fa)continue; dp[now][0]=u; dfs(now,u,h+1); } } int lca(int u,int v){ if(dep[u]<dep[v])swap(u,v); //先跳到相同節點 for(int i=19;i>=0;i--){ //根節點的深度要為1,不然他們可能跳過頭 if(dep[dp[u][i]]>=dep[v]){ u=dp[u][i]; } } if(u==v)return u; for(int i=19;i>=0;i--){ if(dp[u][i]^dp[v][i])u=dp[u][i],v=dp[v][i]; //兩個相同的數異或起來等於0 //看他們的公共祖先 } return dp[u][0]; } int main() { scanf("%d%d",&n,&m); for(int i=0;i<n-1;i++){ int a,b; scanf("%d%d",&a,&b); g[a].push_back(b); g[b].push_back(a); } return 0; }