The deepest common ancestor of and . Distances, path aggregates, path intersections, virtual trees and most path problems reduce to it.

MethodBuildQueryUse
naive climbingfine for tiny trees or a handful of queries
binary liftingdefault: also -th ancestor and path aggregates
DFS order + sparse tablemany LCA queries; beats binary lifting’s constant when is large
Tarjan offline (DSU)—all queries known
Schieber–Vishkinbit tricks, small memory
Farach-Colton–Benderoptimal RMQ-based variant; large constant, rarely worth it over Schieber–Vishkin
HLDfree if HLD is built anyway
link-cut tree amortizedonly when the tree itself changes online
ladder + jump pointers level ancestor-th ancestor in

Binary lifting with path aggregates

template <class T = ll, class Op = plus<T>> struct BinaryLifting {
    int n, lg = 1; Op op; T e; vector<vector<pair<int, T>>> adj; vector<int> dep;
    vector<vector<int>> up; vector<vector<T>> agg;          // up[k][v]: 2^k-th ancestor (root -> root); agg: op over those edges
    BinaryLifting(int n, Op f = Op(), T id = T()) : n(n), op(f), e(id), adj(n), dep(n) { while ((1 << lg) < n) lg++; }
    void addEdge(int u, int v, T w = T(1)) { adj[u].push_back({v, w}), adj[v].push_back({u, w}); }
    void build(int root = 0) {
        up.assign(lg, vector<int>(n, root)); agg.assign(lg, vector<T>(n, e)); vector<int> order{root}; dep[root] = 0;
        for (int i = 0; i < sz(order); i++) { int u = order[i]; for (auto [v, w] : adj[u]) if (v != up[0][u] && v != root) up[0][v] = u, agg[0][v] = w, dep[v] = dep[u] + 1, order.push_back(v); }
        for (int k = 1; k < lg; k++) for (int v : order) { int m = up[k - 1][v]; up[k][v] = up[k - 1][m]; agg[k][v] = op(agg[k - 1][v], agg[k - 1][m]); }
    }
    int jump(int u, int k) const { if (k < 0 || k > dep[u]) return -1; for (int i = 0; k; i++, k >>= 1) if (k & 1) u = up[i][u]; return u; }
    int lca(int u, int v) const {
        if (dep[u] < dep[v]) swap(u, v);
        u = jump(u, dep[u] - dep[v]); if (u == v) return u;
        for (int k = lg - 1; k >= 0; k--) if (up[k][u] != up[k][v]) u = up[k][u], v = up[k][v];
        return up[0][u];
    }
    int dist(int u, int v) const { return dep[u] + dep[v] - 2 * dep[lca(u, v)]; }
    T query(int u, int v) const {                           // op over the edges of the path u - v
        T res = e; if (dep[u] < dep[v]) swap(u, v);
        for (int k = lg - 1; k >= 0; k--) if (dep[u] - (1 << k) >= dep[v]) res = op(res, agg[k][u]), u = up[k][u];
        if (u == v) return res;
        for (int k = lg - 1; k >= 0; k--) if (up[k][u] != up[k][v]) res = op(res, op(agg[k][u], agg[k][v])), u = up[k][u], v = up[k][v];
        return op(res, op(agg[0][u], agg[0][v]));
    }
};

The aggregate needs to be associative and commutative here (max, min, sum, gcd, xor); for non-commutative ones (matrix products, “path as a sequence”) keep upward and downward versions separately.

Non-invertible aggregates

Prefix subtraction works for sums (f(u) - f(lca)-style tricks), but max, min and gcd do not invert: there is no way to recover “the max over this sub-range” by subtracting a prefix max. Always climb explicitly from both endpoints to the LCA and combine as query does above, rather than trying to shortcut with a difference.

Memory: integers — for and that’s 80 MB of int, often too much. Reduce LOG to exactly, switch to the Euler-tour method below if only plain LCA is needed, or use HLD (which is memory).

The same table on any functional graph answers “where after steps” (Functional Graphs) — the only requirement is a single “next” pointer, not a tree:

Applicationup[v][0] is
tree ancestorparent
permutation power
successor / “next greater” chainsnext index
game state after movesthe forced move
sparse table over an array”jump to the first index outside the current window”

The last row is the basis of the classic “minimum number of intervals to cover a range”: precompute for each position the farthest reachable position with one interval, then binary lift to answer coverage queries in .

LCA with a sparse table on DFS order

In preorder, for with : let be the vertex of minimum depth among positions ; then . Uses entries instead of the of the Euler tour. LCA is also the standard prerequisite for virtual trees: it is what lets the pairwise LCAs of marked vertices be found fast enough to build the virtual tree in .

Three different “Euler tour” flattenings show up across this chapter, easy to conflate: (A) the size- preorder subtree tour used here and for subtree updates/queries (Euler Tour, Subtree and Path Queries, Fenwick/segment tree); (B) the size- entry/exit path tour, for path sum/XOR and Mo’s on trees (same note, Fenwick with an inverse op); (C) this note’s size- depth tour, built purely for LCA via RMQ. They solve different problems and are not interchangeable.

struct LCA {
    int n; vector<int> tin, par, dep; vector<vector<pair<int, int>>> sp;   // sp over preorder: (depth, vertex)
    LCA(const vector<vector<int>>& adj, int root) : n(sz(adj)), tin(n), par(n, -1), dep(n) {
        vector<int> st{root}, order; order.reserve(n);
        while (sz(st)) { int u = st.back(); st.pop_back(); tin[u] = sz(order); order.push_back(u);
            for (int v : adj[u]) if (v != par[u]) par[v] = u, dep[v] = dep[u] + 1, st.push_back(v); }
        int K = __lg(n) + 1; sp.assign(K, vector<pair<int, int>>(n));
        for (int i = 0; i < n; i++) sp[0][i] = {dep[order[i]], order[i]};
        for (int k = 1; k < K; k++) for (int i = 0; i + (1 << k) <= n; i++) sp[k][i] = min(sp[k - 1][i], sp[k - 1][i + (1 << (k - 1))]);
    }
    int lca(int u, int v) const {
        if (u == v) return u;
        int l = tin[u], r = tin[v]; if (l > r) swap(l, r);
        int k = __lg(r - l);
        return par[min(sp[k][l + 1], sp[k][r - (1 << k) + 1]).second];
    }
    int dist(int u, int v) const { return dep[u] + dep[v] - 2 * dep[lca(u, v)]; }
};

Tarjan’s offline LCA

DFS; when a vertex finishes, union it into its parent’s set whose representative points at the parent. A query is answered when the second of them is visited: the answer is the current ancestor label of the first one’s set.

vector<int> offlineLCA(const vector<vector<int>>& adj, int root, const vector<pair<int, int>>& qs) {
    int n = sz(adj); DSU d(n); vector<int> anc(n), res(sz(qs)), par(n, -1), it(n); vector<char> vis(n);
    vector<vector<pair<int, int>>> at(n);
    for (int i = 0; i < sz(qs); i++) at[qs[i].first].push_back({qs[i].second, i}), at[qs[i].second].push_back({qs[i].first, i});
    vector<int> st{root}; vis[root] = 1; anc[root] = root;
    while (sz(st)) {
        int u = st.back();
        if (it[u] == 0) for (auto [w, i] : at[u]) if (vis[w]) res[i] = anc[d.find(w)];   // on entering u
        if (it[u] < sz(adj[u])) {
            int v = adj[u][it[u]++];
            if (v == par[u]) continue;
            par[v] = u; vis[v] = 1; anc[v] = v; st.push_back(v);
        } else {
            st.pop_back();
            if (par[u] >= 0) { d.unite(u, par[u]); anc[d.find(u)] = par[u]; }
        }
    }
    return res;
}

Schieber–Vishkin ( build, query)

Number vertices in preorder from 1. is the number in ‘s subtree interval with the most trailing zeros; vertices with equal form vertical paths (“runs”), and maps the tree onto a complete binary tree in in-order numbering so that ancestors map to ancestors. has bit set iff some ancestor of (or ) has of height . The query finds the height of the LCA’s run from with bit operations, then enters that run from both sides.

struct SchieberVishkin {
    int n; vector<int> par, I, dep, head; vector<unsigned> A;
    SchieberVishkin(const vector<vector<int>>& adj, int root) : n(sz(adj)), par(n, -1), I(n), dep(n), head(n + 2), A(n) {
        vector<int> pre(n), sub(n, 1), order, st{root}; order.reserve(n);
        while (sz(st)) { int u = st.back(); st.pop_back(); pre[u] = sz(order) + 1; order.push_back(u);
            for (int v : adj[u]) if (v != par[u]) par[v] = u, dep[v] = dep[u] + 1, st.push_back(v); }
        for (int i = n - 1; i > 0; i--) sub[par[order[i]]] += sub[order[i]];
        for (int u : order) {
            int l = pre[u], r = pre[u] + sub[u] - 1;
            I[u] = r & -(1 << __lg((l - 1) ^ r));              // the most trailing zeros in [l, r]
            A[u] = (par[u] < 0 ? 0u : A[par[u]]) | unsigned(I[u] & -I[u]);
            if (par[u] < 0 || I[par[u]] != I[u]) head[I[u]] = u;  // top of the run
        }
    }
    int enter(int x, int j) const {                         // first vertex of x's root path in the run of height j
        if (__builtin_ctz(A[x]) == j) return x;
        int k = __lg(A[x] & ((1u << j) - 1));
        int Iw = ((I[x] >> (k + 1)) << (k + 1)) | (1 << k);
        return par[head[Iw]];
    }
    int lca(int x, int y) const {
        if (I[x] == I[y]) return dep[x] < dep[y] ? x : y;
        int h = max({__lg(I[x] ^ I[y]), __builtin_ctz(I[x]), __builtin_ctz(I[y])});   // height of LCA of I(x), I(y)
        int j = __builtin_ctz(A[x] & A[y] & (~0u << h));
        int a = enter(x, j), b = enter(y, j);
        return dep[a] < dep[b] ? a : b;
    }
};

Level ancestor in : ladders

Decompose into long paths (each vertex continues into its child of greatest height), extend each path upwards by its own length to get a ladder. To find the -th ancestor, jump with binary lifting; the vertex reached has a path below it of length , so its ladder covers the remaining steps.

struct LadderAncestor {
    int n, LOG = 1; vector<int> dep, par, pos, ladderOf; vector<vector<int>> up, ladder;
    LadderAncestor(const vector<vector<int>>& adj, int root) : n(sz(adj)), dep(n), par(n, -1), pos(n), ladderOf(n), ladder(n) {
        while ((1 << LOG) < n) LOG++;
        vector<int> order{root}, height(n, 1), heavy(n, -1);
        for (int i = 0; i < sz(order); i++) for (int v : adj[order[i]]) if (v != par[order[i]]) par[v] = order[i], dep[v] = dep[order[i]] + 1, order.push_back(v);
        for (int i = n - 1; i >= 0; i--) { int u = order[i]; for (int v : adj[u]) if (v != par[u] && height[v] + 1 > height[u]) height[u] = height[v] + 1, heavy[u] = v; }
        up.assign(LOG, vector<int>(n));
        for (int v = 0; v < n; v++) up[0][v] = par[v] < 0 ? v : par[v];
        for (int k = 1; k < LOG; k++) for (int v = 0; v < n; v++) up[k][v] = up[k - 1][up[k - 1][v]];
        for (int u : order) {
            if (par[u] >= 0 && heavy[par[u]] == u) continue;    // u tops a long path
            vector<int> path, lad;
            for (int v = u; v >= 0; v = heavy[v]) path.push_back(v);
            for (int a = par[u], c = 0; a >= 0 && c < sz(path); a = par[a], c++) lad.push_back(a);
            reverse(all(lad)); int ext = sz(lad);
            lad.insert(lad.end(), all(path));
            for (int i = 0; i < sz(path); i++) pos[path[i]] = ext + i, ladderOf[path[i]] = u;
            ladder[u] = move(lad);
        }
    }
    int kth(int v, int k) const {                           // k-th ancestor, -1 if above the root
        if (k > dep[v]) return -1;
        if (k == 0) return v;
        int h = __lg(k); v = up[h][v]; k -= 1 << h;
        return ladder[ladderOf[v]][pos[v] - k];
    }
};

Path utilities

template <class BL> bool onPath(const BL& t, int a, int b, int x) { return t.dist(a, x) + t.dist(x, b) == t.dist(a, b); }
template <class BL> int kthOnPath(const BL& t, int a, int b, int k) {    // vertex at distance k from a towards b
    int c = t.lca(a, b), da = t.dep[a] - t.dep[c], db = t.dep[b] - t.dep[c];
    return k <= da ? t.jump(a, k) : t.jump(b, da + db - k);
}
template <class BL> tuple<bool, int, int> pathIntersection(const BL& t, int a, int b, int x, int y) {
    array<int, 4> c = {t.lca(a, x), t.lca(a, y), t.lca(b, x), t.lca(b, y)};
    sort(all(c), [&](int p, int q) { return t.dep[p] > t.dep[q]; });
    int u = c[0], v = c[1], l1 = t.lca(a, b), l2 = t.lca(x, y);
    if (u == v && (t.dep[u] < t.dep[l1] || t.dep[u] < t.dep[l2])) return {false, -1, -1};
    return {true, u, v};                                     // the common part is the path u - v
}
FactFormula
LCA of a setLCA of its min-tin and max-tin vertices
meeting point of (minimising total distance)the one of that differs (the deepest)
on path –
additive path updates, queries at the end at ; at the LCA and its parent; subtree sums
intersection of paths –, –the path between the two deepest of the four cross LCAs, if it lies below both path LCAs