import java.util.*; import java.util.stream.Stream; // Answering LCA queries in O(log(n)) with O(n) preprocessing public class Lca { int[] depth; int[] dfs_order; int cnt; int[] first; int[] minPos; int n; void dfs(List[] tree, int u, int d) { depth[u] = d; dfs_order[cnt++] = u; for (int v : tree[u]) if (depth[v] == -1) { dfs(tree, v, d + 1); dfs_order[cnt++] = u; } } void buildTree(int node, int left, int right) { if (left == right) { minPos[node] = dfs_order[left]; return; } int mid = (left + right) >> 1; buildTree(2 * node + 1, left, mid); buildTree(2 * node + 2, mid + 1, right); minPos[node] = depth[minPos[2 * node + 1]] < depth[minPos[2 * node + 2]] ? minPos[2 * node + 1] : minPos[2 * node + 2]; } public Lca(List[] tree, int root) { int nodes = tree.length; depth = new int[nodes]; Arrays.fill(depth, -1); n = 2 * nodes - 1; dfs_order = new int[n]; cnt = 0; dfs(tree, root, 0); minPos = new int[4 * n]; buildTree(0, 0, n - 1); first = new int[nodes]; Arrays.fill(first, -1); for (int i = 0; i < dfs_order.length; i++) if (first[dfs_order[i]] == -1) first[dfs_order[i]] = i; } public int lca(int a, int b) { return minPos(Math.min(first[a], first[b]), Math.max(first[a], first[b]), 0, 0, n - 1); } int minPos(int a, int b, int node, int left, int right) { if (a == left && right == b) return minPos[node]; int mid = (left + right) >> 1; if (a <= mid && b > mid) { int p1 = minPos(a, Math.min(b, mid), 2 * node + 1, left, mid); int p2 = minPos(Math.max(a, mid + 1), b, 2 * node + 2, mid + 1, right); return depth[p1] < depth[p2] ? p1 : p2; } else if (a <= mid) { return minPos(a, Math.min(b, mid), 2 * node + 1, left, mid); } else if (b > mid) { return minPos(Math.max(a, mid + 1), b, 2 * node + 2, mid + 1, right); } else { throw new RuntimeException(); } } // Random test public static void main(String[] args) { Random rnd = new Random(1); for (int step = 0; step < 1000; step++) { int n = rnd.nextInt(50) + 1; List[] tree = getRandomTree(n, rnd); int[] depth = new int[n]; Arrays.fill(depth, -1); int root = rnd.nextInt(n); calcDepth(tree, depth, root, 0); Lca q = new Lca(tree, root); for (int i = 0; i < 1000; i++) { int a = rnd.nextInt(n); int b = rnd.nextInt(n); List path = new ArrayList<>(); getPathFromAtoB(tree, a, b, -1, path); int res1 = q.lca(a, b); int res2 = a; for (int u : path) if (depth[res2] > depth[u]) res2 = u; if (res1 != res2) throw new RuntimeException(); } } System.out.println("Test passed"); } static List[] getRandomTree(int n, Random rnd) { List[] t = Stream.generate(ArrayList::new).limit(n).toArray(List[]::new); int[] p = new int[n]; for (int i = 0, j; i < n; j = rnd.nextInt(i + 1), p[i] = p[j], p[j] = i, i++) ; // random permutation for (int i = 1; i < n; i++) { int parent = p[rnd.nextInt(i)]; t[parent].add(p[i]); t[p[i]].add(parent); } return t; } static void calcDepth(List[] tree, int[] depth, int u, int d) { depth[u] = d; for (int v : tree[u]) if (depth[v] == -1) calcDepth(tree, depth, v, d + 1); } static boolean getPathFromAtoB(List[] tree, int a, int b, int p, List path) { path.add(a); if (a == b) return true; for (int u : tree[a]) if (u != p && getPathFromAtoB(tree, u, b, a, path)) return true; path.remove(path.size() - 1); return false; } }