算法提高之祖孙询问(附LCA)
- 核心思想:最近公共祖先(LCA)
LCA算法
-
向上标记法(不常用):一遍用bool数组从下往上不断标记祖先节点,再一遍再标记
-
倍增法:类似快速幂,讲究的是二进制拼凑出一个数
-
预处理ja[i][j] 表示从第i个节点向上跳2j层能跳到的节点
-
ja[i][j] = ja[ja[i][j-1][j-1] 先跳j-1次,再跳j-1次
-
例如跳11层,k从大到小遍历跳2k层 , 即先跳8层再跳2层最后跳1层
-
-
操作步骤:
-
1.深的节点先跳到和浅的节点同一层
-
2.一起往上跳到公共祖先节点下面的一层
-
-
-
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 40010,M = N*2; int n,m; int q[N]; int depth[N],fa[N][16]; int h[N],e[M],ne[M],idx; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } void bfs(int root) //预处理ja和depth { memset(depth, 0x3f, sizeof depth); depth[0] = 0, depth[root] = 1; //哨兵depth[0] = 0;防止跳出树 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); //从小往上跳 for (int k = 15; k >= 0; k -- ) //先大跳后小跳 if (depth[fa[a][k]] >= depth[b]) //跳2^k步后仍在b下面 就可以跳 a = fa[a][k]; if (a == b) return a; //当前b(=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]; //当前跳到公共祖先下一层 再向上跳2^0 = 1层即可 } int main() { scanf("%d", &n); int root = 0; memset(h, -1, sizeof h); for (int i = 0; i < n; i ++ ) { int a, b; scanf("%d%d", &a, &b); if (b == -1) root = a; else add(a, b), add(b, a); } bfs(root); scanf("%d", &m); while (m -- ) { int a, b; scanf("%d%d", &a, &b); int p = lca(a, b); //得到ab的最近公共祖先p if (p == a) puts("1"); else if (p == b) puts("2"); else puts("0"); } return 0; }