在有根樹T中,兩個節點a、b的最近公共祖先(Lowest Common Ancestors,LCA)是某個點p,使得p同時是a、b的祖先,而且p離根最遠。
在樹上求最短路時,相較於其他的最短路算法,一些快速求LCA的算法有更高的效率。
這裏我們介紹以倍增法爲基礎的LCA算法。(又稱“跳錶法”);
具體過程是使用dfs建樹,維護具體信息,再用倍增法巧妙求得LCA,便於理解記憶
在dfs的過程中維護三個數組:
depth[i],表示i點在樹中的深度;
grand[x][i],表示x的第2^i個祖先的節點編號;
gdis[x][i],表示x到它2^i祖先的距離。
初始化:
const int maxn = 40010;
const int maxlog = 20;//maxlog = log2(maxn)
int grand[maxn][maxlog],gdis[maxn][maxlog];
int depth[maxn];
int n,m,q,s;
void Init(){
//n爲節點個數
s = floor(log(n + 0.0) / log(2.0));//最多能跳的2^i祖先
depth[0]=-1;//根結點的祖先不存在,用-1表示
dfs(1);//以1爲根節點建樹
}
這是dfs過程:
每次dfs先更新x結點的grand和gdis,然後搜索與x相連的結點
void dfs(int x){//dfs建樹
for(int i = 1; i <= s; i++){
grand[x][i] = grand[grand[x][i-1]][i-1];//x的2^i祖先是它2^i-1祖先的2^i-1祖先
gdis[x][i] = gdis[x][i-1] + gdis[grand[x][i-1]][i-1];//x到它2^i祖先的距離 = x到它2^i-1祖先的距離 + x的2^i-1祖先到它2^i-1祖先距離
if(!grand[x][i]) break; //到子樹的根了
}
for(int i = 0; i < G[x].size(); i++){
Edge & e = edges[G[x][i]];
if(e.to != grand[x][0]){//與x相連的結點,不是它的父親,就是他的兒子
depth[e.to] = depth[x] +1;//維護深度
grand[e.to][0] = x;//維護父子關係
dis[e.to][0] = e.dist;//維護距離
dfs(e.to);
}
}
}
這是求LCA過程:
將a,b兩個結點從它們原本的深度向上“跳”,直到它們到達它們的LCA,一邊跳一邊加上邊權
採取先讓深度大的點跳,再讓兩個點一起跳的方式
int LCA(int a, int b){
if(depth[a] > depth[b]) swap(a, b);//保證a在b上面,便於計算
int ans = 0;
for(int i = s; i >= 0; i--) //類似於二進制拆分,從大到小嚐試
if(depth[a] < depth[b] && depth[grand[b][i]] >= depth[a])//a在b下面且b向上跳後不會到a上面
ans +=gdis[b][i], b =grand[b][i];//先把深度較大的b往上跳
for(int i = s; i >= 0; i--)
if(grand[a][i] != grand[b][i])//a,b的2^i祖先不一樣 => 沒跳到同樣深度位置,接着跳
ans += gdis[a][i], ans += gdis[b][i], a = grand[a][i], b = grand[b][i];//一起往上跳
if(a != b) ans += gdis[a][0], ans += gdis[b][0];//沒跳到一個地方,那麼再往上一個結點就是它們的LCA
return ans;
}
妙不可言的模板題鏈接:
[USACO FEB04]距離諮詢
注:存圖方式爲劉汝佳《入門經典》介紹的vector鄰接表,代碼如下:
struct Edge{
int from,to,dist;
Edge(int u,int v,int w):from(u),to(v),dist(w){}
};
const int maxn = 40010;//節點數
vector<Edge> edges;//邊的具體信息
vector<int> G[maxn];//邊的編號
void addEdge(int u, int v, int w){//加入一條邊(無向圖)
edges.push_back(Edge(u,v,w));
edges.push_back(Edge(v,u,w));
int size = edges.size();
G[u].push_back(size-2);
G[v].push_back(size-1);
}