最近公共祖先(LCA)
\(\text{LCA}\)
定义 性质:
在有根树上,\(\text{LCA}\) 是指一个点集的公共祖先中深度最大的点。
-
\(\text{LCA}\) 具有可并性。
-
\(\text{LCA}(u,v)\) 在 \(u,v\) 间的路径上。
-
设 \(h(i)\) 表示 \(i\) 与根的距离,则两点 \(u,v\) 的距离 \(=h(u)+h(v)-2h(\text{LCA}(u,v))\)。
求法:
倍增。
设 \(fa_{u,i}\) 表示 \(u\) 的第 \(2^i\) 级祖先,在求 \(\text{LCA}(u,v)\) 时,先将 \(u,v\) 跳转到同一深度,再枚举 \(k\),尝试将 \(u,v\) 跳到自己的 \(k\) 级祖先,并保证不会多跳到它们的公共祖先上,即保证它们不同,如果它们不相等,则跳,否则不跳。
graph LR
LCA((LCA))-->根
u-->t0(...)-->u跳到这里-->LCA
Node1-->t1(v跳到这里)-->LCA
v(v)-->t1
Node2-->t1
到最后时,\(u,v\) 恰好是 \(\text{LCA}\) 的孩子,\(fa_{u,0}\) 即为 \(u,v\) 的 \(\text{LCA}\)。
预处理 \(fa\) 的方法:\(DFS\),\(fa_{u,0}\) 为自己的父亲,因为 \(2^{i-1}+2^{i-1}=2^i\),所以 \(u\) 的第 \(2^{i-1}\) 级祖先的第 \(2^{i-1}\) 级祖先就是 \(u\) 的第 \(2^{i-1}+2^{i-1}=2^i\) 级祖先,所以转移方程为:
\[\large{fa_{u,i}=fa_{fa_{u,i-1},i-1}}
\]
graph LR
u--2^i-1-->v--2^i-1-->w
u--2^i-1 + 2^i-1 = 2^i-->w
时间复杂度:设询问 \(m\) 次,\(\Theta(n\log n+m\log n)\)。
代码:
#include<iostream>
#include<vector>
#define int long long
using namespace std;
const int N = 500010;
int lg[N];
int n, m, rt;
vector<int> G[N];
int de[N], fa[N][25];
void dfs(int fat, int u) // 预处理
{
fa[u][0] = fat;
de[u] = de[fat] + 1; // 初始化
for(int i=1; i<=lg[de[u]]; i++)
fa[u][i] = fa[fa[u][i-1]][i-1]; // 转移
for(int i=0; i<G[u].size(); i++)
{
int v = G[u][i];
if(v == fat)
continue;
dfs(u, v); // 搜索
}
}
int LCA(int x, int y)
{
if(de[x] < de[y]) // 保证y深于x,便于计算
swap(x, y);
while(de[x] > de[y])
x = fa[x][lg[de[x] - de[y]] - 1]; // 统一深度
if(x == y)
return x; // 特判
for(int k=lg[de[x]]-1; k>=0; k--)
if(fa[x][k] != fa[y][k])
x = fa[x][k],
y = fa[y][k]; // 跳转到LCA的下方
return fa[x][0];
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
for(int i=1; i<N; i++)
lg[i] = lg[i-1] + ((1 << lg[i-1]) == i);
cin >> n >> m >> rt;
for(int i=1; i<n; i++)
{
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(0, rt);
while(m --)
{
int x, y;
cin >> x >> y;
cout << LCA(x, y) << '\n';
}
return 0;
}

浙公网安备 33010602011771号