跳到主要内容

LCA倍增算法

预处理(nlogn) + 查询(logn) 普通的LCA算法是先用DFS预处理每个点的深度,然后第一个点走到根节点,走过的点打上标记,在让第二个点向上走,遇到有标记的点说明找到了最近公共祖先。 LCA算法的核心是不用一个一个走,可以利用二进制的思想一段一段走。

  • depth[u]存储u的深度
  • fa[u][i]存储u点向上跳2i2^i层的祖先节点i=0,1,2,3...i=0,1,2,3...
  1. 先用DFS或者BFS搜索一遍,倍增递推f[u][i]=fa[fa[u][i1]][i1]f[u][i]=fa[fa[u][i-1]][i-1]
    1. 上式是先让u点跳2i12^{i-1}层,得到fa[u][i1]fa[u][i-1]
    2. 然后再向上跳2i12^{i-1}层得到fa[fa[u][i1]][i1]fa[fa[u][i-1]][i-1]
  2. 利用ST表求LCA
    1. 先将u,vu,v跳到同一层,设u,vu,v两点的深度之差为yy,将y进行二进制拆分,可以将y次游标跳跃优化为“y的二进制表示所含1的个数”次游标跳跃
    2. u,vu,v一起跳到LCA的下一层,从最大的ii开始循环,一直尝试到0,最后u,vu,v一定能停在LCA的下一层。
#include <bits/stdc++.h>

using namespace std;
const int N = 40010, M = N * 2;

int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][16];
int q[N];

void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void bfs(int root)
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[root] = 1;
int hh = 0, tt = 0;
q[0] = root;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[ ++ tt] = j;
fa[j][0] = t;
for (int k = 1; k <= 15; k ++ )
fa[j][k] = fa[fa[j][k - 1]][k - 1];
}
}
}
}


int lca(int a, int b) {
if (depth[a] < depth[b]) swap(a, b);//让a比b深,然后每次跳的就是a
for (int k = 15; k >= 0; k--)
if (depth[fa[a][k]] >= depth[b])//让a跳到和b一个深度
a = fa[a][k];
if (a == b) return a;
for (int k = 15; k >= 0; k--) {
if (fa[a][k] != fa[b][k]) {
a = fa[a][k];
b = fa[b][k];
}
}
return fa[a][0];
}

int main() {
cin >> n;
int root = 0;
memset(h, -1, sizeof h);
for (int i = 0; i < n; i++) {
int a, b;
cin >> a >> b;
if (b == -1) root = a;
else add(a, b), add(b, a);
}
bfs(root);

cin >> m;
while (m--) {
int a, b;
cin >> a >> b;
int p = lca(a, b);
if (p == a) puts("1");
else if (p == b) puts("2");
else puts("0");
}
return 0;
}