import java.util.*; import java.util.stream.Stream; // Answering LCA queries in O(1) with O(n) preprocessing public class LcaSchieberVishkin { int[] parent; int[] preOrder; int[] I; int[] head; int[] A; int time; void dfs1(List[] tree, int u, int p) { parent[u] = p; I[u] = preOrder[u] = time++; for (int v : tree[u]) { if (v == p) continue; dfs1(tree, v, u); if (Integer.lowestOneBit(I[v]) > Integer.lowestOneBit(I[u])) { I[u] = I[v]; } } head[I[u]] = u; } void dfs2(List[] tree, int u, int p, int up) { A[u] = up | Integer.lowestOneBit(I[u]); for (int v : tree[u]) { if (v == p) continue; dfs2(tree, v, u, A[u]); } } public LcaSchieberVishkin(List[] tree, int root) { int n = tree.length; preOrder = new int[n]; I = new int[n]; head = new int[n]; A = new int[n]; parent = new int[n]; dfs1(tree, root, -1); dfs2(tree, root, -1, 0); } int enterIntoStrip(int x, int hz) { if (Integer.lowestOneBit(I[x]) == hz) return x; int hw = Integer.highestOneBit(A[x] & (hz - 1)); return parent[head[I[x] & (~hw + 1) | hw]]; } public int lca(int x, int y) { int hb = I[x] == I[y] ? Integer.lowestOneBit(I[x]) : Integer.highestOneBit(I[x] ^ I[y]); int hz = Integer.lowestOneBit(A[x] & A[y] & (~hb + 1)); int ex = enterIntoStrip(x, hz); int ey = enterIntoStrip(y, hz); return preOrder[ex] < preOrder[ey] ? ex : ey; } // 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); LcaSchieberVishkin q = new LcaSchieberVishkin(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; } }