最近公共祖先(\(\rm Least\,Common\,Ancestors\)),簡記為 \(\rm LCA\)。顧名思義就是一棵樹中的某兩個節點的公共的祖先中離他們最近,即深度最大的那個。
舉個例子:
上圖中 \(8\) 和 \(6\) 的 LCA 就是 \(1\)。
那么怎么求 LCA 呢?
1. 向上標記法
思路十分簡單。
我們現在要求 \(\operatorname{LCA(x,y)}\)。首先從 \(x\) 向上爬到根節點 \(rt\),經過的每一個節點都打上標記。然后從 \(y\) 向上到 \(rt\),遇到的第一個有標記的節點就是 \(LCA\)。
代碼實現很簡單,就不給了。
2. 同步前進法
求 \(\operatorname{LCA(x,y)}\) 的具體方法為:
- 先讓 \(x\) 的深度大於或等於 \(y\)。這相當於數學上的一個假設:\(dep(x)\ge dep(y)\)(\(dep\) 代表深度)
- 不停地將 \(x\) 往上跳,直到 \(x\) 和 \(y\) 深度相等。
- 特判:若 \(x=y\) 那 \(LCA\) 就是 \(x\) 了。
- 否則在保證 \(fa(x)\ne fa(y)\)(\(fa\) 代表父親) 的情況下 \(x\) 和 \(y\) 同時向上跳一格。
- \(LCA\) 就是 \(x\) 的父親。
以上面的 \(8\) 和 \(6\) 為例:
- \(dep(8)>dep(6)\),無需交換。
- \(8\) 跳到 \(4\),\(dep(4)=dep(6)\)。
- \(4\ne6\)。
- \(fa(4)=2\ne fa(6)=3\),\(4\) 跳到 \(2\),\(6\) 跳到 \(3\);\(fa(2)=1=fa(3)\),停止。
- \(\operatorname{LCA(8,6)}=fa(2)=1\)。
在此之前還需 dfs 一遍求出 \(dep\) 和 \(fa\)。
void dfs(int u, int father)
{
fa[u] = father;
dep[u] = dep[father] + 1; //u深度即father深度加1
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v != father)
{
dfs(v, u);
}
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
{
swap(x, y); //讓x的深度大於或等於y
}
while (dep[x] > dep[y])
{
x = fa[x]; //向上跳
}
if (x == y)
{
return x; //特判
}
while (fa[x] != fa[y])
{
x = fa[x]; //同時跳
y = fa[y];
}
return fa[x]; //LCA是父親
}
但以上兩種方法時間復雜度均為 \(\operatorname{O}(nm)\)……
然后洛谷 A 了。
對於 \(100\%\) 的數據,\(N\le500000\),\(M\le500000\)。
數據太水了啊啊啊!!!
出數據的真良心。。。
3. 倍增
上面暴力算法顯然過慢,所以我們要使用倍增算法,也是一種空間換時間的策略。
先預處理出 \(lg\) 數組,用來保存 \(\left\lfloor log_2x\right\rfloor\)
for (int i = 2; i <= n; i++)
{
lg[i] = lg[i >> 1] + 1;
}
記 \(fa(x)(i)\) 為 \(x\) 的第 \(2^i\) 級祖先,dfs 時要算 \(fa(u)(i)\)。
LCA 的第 \(2\) 步,\(x\) 直接向上跳 \(lg(dep(x)-dep(y))\) 格。第 \(4\) 步時,我們遍歷 \(i=lg(dep(x))+1\sim0\),每次 \(x\) 和 \(y\) 同時向上跳 \(2^i\) 格。
void dfs(int u, int father)
{
fa[u][0] = father; //u的第1級祖先就是父親
dep[u] = dep[father] + 1;
for (int i = 1; i <= lg[dep[u]] + 1; i++)
{
fa[u][i] = fa[fa[u][i - 1]][i - 1];
} //算fa(u)(i)
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v != father)
{
dfs(v, u);
}
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
{
swap(x, y);
}
while (dep[x] > dep[y])
{
x = fa[x][lg[dep[x] - dep[y]]];
}
if (x == y)
{
return x;
}
for (int i = lg[dep[x]] + 1; i >= 0; i--)
{
if (fa[x][i] != fa[y][i])
{
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
復雜度分析:
時間 | 空間 | |
---|---|---|
暴力 | 預處理 \(\operatorname{O}(n)\),每次詢問 \(\operatorname{O}(n)\) | \(\operatorname{O}(n)\) |
倍增 | 預處理 \(\operatorname{O}(n\log n)\),每次詢問 \(\operatorname{O}(\log n)\) | \(\operatorname{O}(n\log n)\) |
暴力:
倍增:
用 LCA 求樹上兩點間的最短距離
還是以這張圖為例主要是我懶
\(8\) 和 \(5\):
預處理出每個點到根節點的距離 \(dis(x)\),這里因為是無權邊,\(dis(x)\) 直接就是 \(dep(x)\) 了。
先用 \(dis(8)\) 加上 \(dis(5)\),此時多加了 \(2\) 遍 \(dis(2)\),故 \(8\) 和 \(5\) 的距離就應該是 \(dis(8)+dis(5)-2\times dis(2)\),即樹上 \(x\) 和 \(y\) 兩點間的最短距離為 \(dis(x)+dis(y)-2\times dis(LCA)\)。