Patterns/Part II - Linked Structures/Offline LCA

Pattern Reference

Offline LCA

"Tarjan's offline algorithm for batch LCA queries using DSU."

Loading...

Deep Dive Tutorial

Tarjan's LCA: DFS the tree. When we finish processing a node u, union(u, parent(u)) and mark u as "visited." For each query (u, v): when we encounter u and v is already visited, LCA(u, v) = find(v) (the root of v's component, which has been promoted to be the LCA ancestor). Process queries for each node when that node is being finished.

Tarjan's offline LCA
function tarjanLCA(n, edges, queries) {
    const adj = Array.from({length: n}, () => []);
    for (const [u, v] of edges) { adj[u].push(v); adj[v].push(u); }

    // Group queries by node
    const queryList = Array.from({length: n}, () => []);
    for (let i = 0; i < queries.length; i++) {
        const [u, v] = queries[i];
        queryList[u].push([v, i]);
        queryList[v].push([u, i]);
    }

    const parent = Array.from({length: n}, (_, i) => i);
    const find = x => parent[x] === x ? x : (parent[x] = find(parent[x]));
    const visited = new Array(n).fill(false);
    const answers = new Array(queries.length);
    const ancestor = Array.from({length: n}, (_, i) => i);

    function dfs(u, p) {
        ancestor[u] = u;
        for (const v of adj[u]) {
            if (v === p) continue;
            dfs(v, u);
            // Union v into u's component, ancestor of root = u
            parent[find(v)] = u;
            ancestor[find(u)] = u;
        }
        visited[u] = true;
        for (const [v, qi] of queryList[u]) {
            if (visited[v]) {
                answers[qi] = ancestor[find(v)];
            }
        }
    }

    dfs(0, -1);
    return answers;
}

Worked Problems

mountain
Tarjan's offline LCA vs Binary Lifting:
- Tarjan's: O(n + q) total, requires all queries upfront, simpler code
- Binary lifting: O(n log n) build, O(log n) per query, supports online queries

Tarjan's key invariant: When node u is finished and v is already visited, find(v) = LCA(u,v). The Union-Find "promotes" each finished subtree to be represented by its parent (the LCA of any query involving both a node in the subtree and any already-visited node).

Use case: When you have many LCA queries and can process all at once — preprocessing for path sum queries, counting edges on paths, etc.