AcWing 1172:祖孙询问 ← 倍增法求LCA(DFS预处理)
【题目来源】
https://www.acwing.com/problem/content/1174/
【题目描述】
给定一棵包含 n 个节点的有根无向树,节点编号互不相同,但不一定是 1∼n。
有 m 个询问,每个询问给出了一对节点的编号 x 和 y,询问 x 与 y 的祖孙关系。
【输入格式】
输入第一行包括一个整数,表示节点个数;
接下来 n 行每行一对整数 a 和 b,表示 a 和 b 之间有一条无向边。如果 b 是 -1,那么 a 就是树的根;
第 n+2 行是一个整数 m 表示询问个数;
接下来 m 行,每行两个不同的正整数 x 和 y,表示一个询问。
【输出格式】
对于每一个询问,若 x 是 y 的祖先则输出 1,若 y 是 x 的祖先则输出 2,否则输出 0。
【数据范围】
1≤n,m≤4×10^4,
1≤每个节点的编号≤4×10^4
【输入样例】
10
234 -1
12 234
13 234
14 234
15 234
16 234
17 234
18 234
19 234
233 19
5
234 233
233 12
233 13
233 15
233 19
【输出样例】
1
0
0
0
2
【算法分析】
(一)倍增法求 LCA 的代码模板
int getLCA(int x,int y) {if(dep[x]<dep[y]) swap(x,y);for(int i=LOG; i>=0; i--) {if(dep[f[x][i]]>=dep[y]) x=f[x][i];}if(x==y) return x;for(int i=LOG; i>=0; i--) {if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];}return f[x][0];
}
(二)倍增法求 LCA(最近公共祖先)的算法思想
倍增法求 LCA(最近公共祖先)的算法思想是通过预处理和二进制跳转来高效解决树结构中的祖先查询问题。以下是其核心思想分析:
● 预处理阶段
在 BFS(或 DFS)中,通过 f[i][j] = f[f[i][j-1]][j-1] 完成预处理,时间复杂度为O(nlogn)。其中,f[i][j] 表示 i 的 2^j 辈祖先。由于 2^j=2^{j-1}+2^{j-1},故有 f[i][j]=f[f[i][j-1]][j-1]。显然,f[i][0] 就是 i 的 2^0=1 辈祖先,即 i 的父结点。其中,j∈[1, logn],n 为树的结点个数。
● 深度对齐
若两节点深度不同,通过二进制拆分(从高位到低位尝试跳转)将较深节点上提到与另一节点同层。例如,在上文模板代码中通过 if(dep[f[x][i]]>=dep[y]) x=f[x][i]; 实现此逻辑。
● 同步跳转找LCA
当两节点同层后,若不相同,则从最大可能的步长(LOG)开始尝试同步跳转。若跳转后祖先不同(f[x][i]!=f[y][i]),则更新节点位置,最终 LCA 为跳转后的父节点(f[x][0])。
● 时间复杂度优化
查询阶段仅需 O(logn) 时间,利用二进制分解将线性搜索转化为对数级跳转,显著提升效率。
该算法结合了动态规划的预处理和二进制拆分思想,适用于静态树的频繁 LCA 查询场景。
【算法代码】
#include <bits/stdc++.h>
using namespace std;const int N=4e4+5;
const int LOG=16; //The logarithm of N to the base 2
const int M=N<<1;
int h[N],e[M],ne[M],idx;
int dep[N],f[N][LOG+1];
int q[N];void add(int a,int b) {e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}void dfs(int u,int fa) {dep[u]=dep[fa]+1;f[u][0]=fa;for(int i=1; i<=LOG; i++) {f[u][i]=f[f[u][i-1]][i-1];}for(int i=h[u]; i!=-1; i=ne[i]) {int j=e[i];if(j!=fa) dfs(j,u);}
}int getLCA(int u, int v) {if(dep[u]<dep[v]) swap(u,v);for(int i=LOG; i>=0; i--) {if(dep[f[u][i]]>=dep[v]) u=f[u][i];}if(u==v) return u;for(int i=LOG; i>=0; i--) {if(f[u][i]!=f[v][i]) {u=f[u][i], v=f[v][i];}}return f[u][0];
}int main() {int n,m,root;cin>>n;memset(h,-1,sizeof h);while(n--) {int a,b;cin>>a>>b;if(b!=-1) {add(a,b);add(b,a);} else root=a;}dfs(root,0);cin>>m;while(m--) {int a,b;cin>>a>>b;int lca=getLCA(a, b);if(a==lca) cout<<"1\n";else if(b==lca) cout<<"2\n";else cout<<"0\n";}return 0;
}/*
in:
10
6 5
10 4
7 2
2 4
1 2
5 2
4 3
8 2
9 5
3 -1
10
3 8
5 7
7 4
10 6
3 4
7 2
3 4
1 4
5 2
2 7out:
1
0
2
0
1
2
1
2
2
1
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/139718939
https://blog.csdn.net/hnjzsyjyj/article/details/152178089
https://blog.csdn.net/hnjzsyjyj/article/details/152128254
https://blog.csdn.net/hnjzsyjyj/article/details/152123136
https://www.acwing.com/solution/content/14146/