问题引入:什么是公共祖先?
倍增法求LCA原理:
本质上是一个dp,类似于之前讲过的ST表,fa[i][j]表示i号节点,往上走2的j次方所到的结点,当dep[i]-pow(2,j)>=1时fa[i][j]有效(假设根节点深度为1)这个fa数组可以用dfs实现,我们看下面例子:
由此我们可以得到fa数组的状态转移方程:
fa[x][i] = fa[fa[x][i - 1]][i - 1];
然后我们采用贪心的思想,查询LCA(x,y)时,假设X深度更深,然后从大到小枚举j,当fa[x][j]的深度不超过y的深度时,x才能往上跳,也就是说要让x往上跳,但是不能超过y,又要尽可能接近y,跳到两者的深度相等即dep[x]=dep[y],此时如果x==y,直接返回公共祖先,如果不相等,x和y同时往上跳,,最后一定会停在LCA的下方一层,我们直接返回fa[x][0]即可,即再向上跳一层,如下图:
我们要找6和10的LCA,令10号点为x,然后x不断向上跳,跳到7号点的位置,此时两者再往上跳就会到3就是公共祖先,所以我们返回fa[x][0]即可,即向上走一层,跳的时候只能一层一层跳否则会出现问题
倍增法求LCA的模板:
步骤1:dfs求fa数组(父亲数组):
void dfs(int x, int p)//p是父亲
{
dep[x] = dep[p] + 1;
fa[x][0] = p;
for (int i = 1; i <= 20; ++i)fa[x][i] = fa[fa[x][i - 1]][i - 1];//转移方程
for (const auto& y : g[x])
{
if (y == p)continue;
dfs(y, x);
}
}
步骤2:LCA函数求公共祖先
int lca(int x, int y)
{
if (dep[x] < dep[y])swap(x, y);//如果x深度比y小,交换x和y,使得x深度更深
for (int i = 20; i >= 0; --i)if (dep[fa[x][i]] >= dep[y])x = fa[x][i];//贪心思想,i从大到小,x向上跳过程中,深度不能超过y
if (x == y)return x;//跳跃过程x不能等于y
for (int i = 20; i >= 0; --i)if (fa[x][i] != fa[y][i])x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
题目:最近公共祖先
代码示例:
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e5 + 9;
int dep[N], fa[N][24];
vector<int> g[N];
void dfs(int x, int p)
{
dep[x] = dep[p] + 1;
fa[x][0] = p;
for (int i = 1; i <= 20; ++i)fa[x][i] = fa[fa[x][i - 1]][i - 1];
for (const auto& y : g[x])
{
if (y == p)continue;
dfs(y, x);
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])swap(x, y);
for (int i = 20; i >= 0; --i)if (dep[fa[x][i]] >= dep[y])x = fa[x][i];
if (x == y)return x;
for (int i = 20; i >= 0; --i)if (fa[x][i] != fa[y][i])x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main()
{
int n; cin >> n;
for (int i = 1; i <n; ++i)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);//读入双向边
}
dfs(1, 0);
int m; cin >> m;
while (m--)
{
int x, y; cin >> x >> y;
cout << lca(x, y) << endl;
}
return 0;
}
运行结果: