LCA

方法1:向上标记法

时间复杂度:O(n)

方法2:倍增

时间复杂度:预处理:O(nlogn)
LCA复杂度:O(logn)
fa[i][j]表示从i开始,向上走2j步能走到的节点,0 <= j <= logn
depth[i]表示i所在深度,规定根节点深度为1
哨兵:如果从i开始,向上走2j步会跳过根节点,那么fa[i][j] = 0, depth[0] = 0

步骤:

  1. 先将两点跳至同一层,即将深度大的j跳至深度低的节点i所在层

二进制凑depth[j] - depth[i]

  1. 两个点同时往上跳直至最近公共祖先的下一层

AcWing 1172. 祖孙询问

  1. import java.util.*;
  2. public class Main {
  3. static final int N = 40010, M = N * 2, P = 16;
  4. static int[] h = new int[N], e = new int[M], ne = new int[M];
  5. static int n, idx;
  6. static int[] depth = new int[N];
  7. static int[][] f = new int[N][P];
  8. static int[] q = new int[N];
  9. public static void main(String[] args) {
  10. Scanner sc = new Scanner(System.in);
  11. n = sc.nextInt();
  12. Arrays.fill(h, -1);
  13. int root = -1;
  14. for (int i = 1; i <= n; i++) {
  15. int a = sc.nextInt(), b = sc.nextInt();
  16. if (b == -1) root = a;
  17. else {
  18. add(b, a);
  19. add(a, b);
  20. }
  21. }
  22. bfs(root);
  23. int query = sc.nextInt();
  24. while (query-- > 0) {
  25. int a = sc.nextInt(), b = sc.nextInt();
  26. int p = lca(a, b);
  27. if (p == a) System.out.println("1");
  28. else if (p == b) System.out.println("2");
  29. else System.out.println("0");
  30. }
  31. }
  32. static void bfs(int root) {
  33. int hh = 0, tt = 0;
  34. q[0] = root;
  35. Arrays.fill(depth, 0x3f3f3f3f);
  36. depth[0] = 0;
  37. depth[root] = 1;
  38. while (hh <= tt) {
  39. int u = q[hh++];
  40. for (int i = h[u]; i != -1; i = ne[i]) {
  41. int j = e[i];
  42. if (depth[j] > depth[u] + 1) {
  43. depth[j] = depth[u] + 1;
  44. f[j][0] = u;
  45. q[++tt] = j;
  46. for (int k = 1; k < P; k++) {
  47. f[j][k] = f[f[j][k - 1]][k - 1];
  48. }
  49. }
  50. }
  51. }
  52. }
  53. static int lca(int a, int b) {
  54. if (depth[a] < depth[b]) {
  55. int t = a;
  56. a = b;
  57. b = t;
  58. }
  59. for (int i = P - 1; i >= 0; i--) {
  60. if (depth[f[a][i]] >= depth[b])
  61. a = f[a][i];
  62. }
  63. if (a == b) return a;
  64. for (int i = P - 1; i >= 0; i--) {
  65. if (f[a][i] != f[b][i]) {
  66. a = f[a][i];
  67. b = f[b][i];
  68. }
  69. }
  70. return f[a][0];
  71. }
  72. static void add(int a, int b) {
  73. e[idx] = b;
  74. ne[idx] = h[a];
  75. h[a] = idx++;
  76. }
  77. }

方法3:Tarjan-离线LCA

时间复杂度:O(n + m)
在深度优先搜索时,将所有点分为三大类

  • 已经遍历过且回溯过的点,标记为2
  • 正在搜索的分支,标记为1
  • 还未搜索到的点,标记为0

对于已经遍历且回溯过的点,在函数结束时将其并入其子树所在根节点的集和(并查集)。
对于当前正在搜索的点,处理LCA查询,另一个节点是那些已经被标记为2的节点,他们的公共祖先就是当前点所在分支上的某个点。

:::info 求树上任意两点的距离:预处理每个点到根节点的距离,再结合最近公共祖先
例如:a, b, lca(a, b) = c,dist指每个点到根节点的距离
d[a, b] = dist[a] + dist[b] - 2 * dist[c] ::: AcWing 1171. 距离

  1. import java.util.*;
  2. public class Main {
  3. static final int N = 10010, M = N * 2;
  4. static int[] h = new int[N], e = new int[M], ne = new int[M], w = new int[M];
  5. static int idx;
  6. static int[] fa = new int[N];
  7. static int[] dist = new int[N];
  8. static int[] res = new int[M];
  9. static List<int[]>[] list = new ArrayList[M];
  10. static int n, m;
  11. static int[] st = new int[N];
  12. public static void main(String[] args) {
  13. Scanner sc = new Scanner(System.in);
  14. n = sc.nextInt();
  15. m = sc.nextInt();
  16. Arrays.fill(h, -1);
  17. for (int i = 0; i < n - 1; i++) {
  18. int a = sc.nextInt(), b = sc.nextInt(), c = sc.nextInt();
  19. add(a, b, c);
  20. add(b, a, c);
  21. }
  22. for (int i = 0; i < m; i++) {
  23. int x = sc.nextInt(), y = sc.nextInt();
  24. if (list[x] == null) list[x] = new ArrayList<>();
  25. if (list[y] == null) list[y] = new ArrayList<>();
  26. list[x].add(new int[]{y, i});
  27. list[y].add(new int[]{x, i});
  28. }
  29. for (int i = 0; i < N; i++)
  30. fa[i] = i;
  31. dfs(1, -1);
  32. tarjan(1);
  33. for (int i = 0; i < m; i++)
  34. System.out.println(res[i]);
  35. }
  36. static void dfs(int u, int p) {
  37. for (int i = h[u]; i != -1; i = ne[i]) {
  38. int j = e[i], c = w[i];
  39. if (j == p) continue;
  40. dist[j] = dist[u] + c;
  41. dfs(j, u);
  42. }
  43. }
  44. static void tarjan(int u) {
  45. st[u] = 1;
  46. for (int i = h[u]; i != -1; i = ne[i]) {
  47. int j = e[i];
  48. if (st[j] == 0) {
  49. tarjan(j);
  50. fa[j] = u;
  51. }
  52. }
  53. if (list[u] != null) {
  54. for (int[] pair : list[u]) {
  55. int v = pair[0], id = pair[1];
  56. if (st[v] == 2) {
  57. int anc = find(v);
  58. res[id] = dist[u] + dist[v] - 2 * dist[anc];
  59. }
  60. }
  61. }
  62. st[u] = 2;
  63. }
  64. static void add(int a, int b, int c) {
  65. e[idx] = b;
  66. w[idx] = c;
  67. ne[idx] = h[a];
  68. h[a] = idx++;
  69. }
  70. static int find(int x) {
  71. return fa[x] == x ? x : (fa[x] = find(fa[x]));
  72. }
  73. }

应用

结合次小生成树

AcWing 356. 次小生成树
可以用LCA快速求出任意两个节点之间的最大权值边

结合树上 差分

AcWing 352. 闇の連鎖

  1. int p = lca(a, b);
  2. diff[a]++;
  3. diff[b]++;
  4. diff[p] -= 2;