最近公共祖先(\(\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)\)。