oiwiki参考文章
最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
求LCA的方法
以LCA(u,v)表示u和v的最近公共祖先
- 朴素算法
先将深度大的点向上调整至和小的点相同,然后两个点同时向上移动,直到两个点移动到同一个点
如果这棵树建的比较平衡,用这种方法的时间复杂度为O(logn),但是遇到一条链特别长的情况时,复杂度可能会退化到O(n)
- 倍增算法
用F[i][j]来表示i的第2^j个父节点。
先将深度大的点向上调整至与小的点相同,然后从最大的 j 开始不断尝试,直到第一次出现 F[u][j] != F[v][j],令u = F[u][j],v = F[v][j],因为LCA(F[u][j],F[v][j]) = LCA(u,v)。然后继续循环直到u == v
即使在极端条件下时间复杂度也为O(logn)
例题:HDU2586
#include <bits/stdc++.h>
#define N 1005
#define M 32
#define mem(a, v) memset(a, v, sizeof(a))
#define fre(f) freopen(f ".in", "r", stdin)
#define inf 0x3f3f3f3f
using namespace std;
vector<int> v[N];
vector<int> w[N];
int fa[N][M], cost[N][M], deep[N];
void dfs(int node, int parent) //图转树
{
fa[node][0] = parent;
deep[node] = deep[parent] + 1;
//初始化fa数组和cost数组
for (int i = 1; i < M; i++)
{
fa[node][i] = fa[fa[node][i - 1]][i - 1];
cost[node][i] = cost[node][i - 1] + cost[fa[node][i - 1]][i - 1];
}
//从子树继续dfs
for (int i = 0; i < v[node].size(); i++)
{
int x = v[node][i], y = w[node][i];
if (x == parent) continue;
cost[x][0] = y;
dfs(x, node);
}
}
int lca(int x, int y) //返回从x到y的最短路径长度
{
if (deep[x] < deep[y]) swap(x, y);
int delta = deep[x] - deep[y];
int ans = 0;
//将x上移,同时加上cost
for (int i = 0; delta; i++, delta >>= 1)
if (delta & 1) ans += cost[x][i], x = fa[x][i];
//如果上移后处于同一结点则直接返回
if (x == y) return ans;
//否则从M-1开始找到不相同的点进行倍增搜索
for (int i = M - 1; i > 0; i--)
{
if (fa[x][i] != fa[y][i])
{
ans += cost[x][i] + cost[y][i];
x = fa[x][i];
y = fa[y][i];
}
}
//最后加上i=0的情况
ans += cost[x][0] + cost[y][0];
return ans;
}
int main()
{
mem(fa, 0);
mem(cost, 0);
deep[0] = 0;
int t, n, m;
scanf("%d", &t);
while (t--)
{
for (int i = 0; i < N; i++)
v[i].clear(), w[i].clear();
scanf("%d%d", &n, &m);
for (int i = 1; i < n; i++)
{
int x, y, ww;
scanf("%d%d%d", &x, &y, &ww);
v[x].push_back(y);
w[x].push_back(ww);
v[y].push_back(x);
w[y].push_back(ww);
}
dfs(1, 0);
for (int i = 0; i < m; i++)
{
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y));
}
}
return 0;
}