The deepest common ancestor of and . Distances, path aggregates, path intersections, virtual trees and most path problems reduce to it.
| Method | Build | Query | Use |
|---|---|---|---|
| naive climbing | fine for tiny trees or a handful of queries | ||
| binary lifting | default: also -th ancestor and path aggregates | ||
| DFS order + sparse table | many LCA queries; beats binary lifting’s constant when is large | ||
| Tarjan offline (DSU) | — | all queries known | |
| Schieber–Vishkin | bit tricks, small memory | ||
| Farach-Colton–Bender | optimal RMQ-based variant; large constant, rarely worth it over Schieber–Vishkin | ||
| HLD | free if HLD is built anyway | ||
| link-cut tree | amortized | only 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 asquerydoes 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:
| Application | up[v][0] is |
|---|---|
| tree ancestor | parent |
| permutation power | |
| successor / “next greater” chains | next index |
| game state after moves | the 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 .
Proof: binary lifting
up[k][v]is correct by induction on (two jumps of ), saturating at the root.jumpdecomposes into powers of two. Inlca, after equalising depths, means one was an ancestor of the other. Otherwise the invariant ”, same depth, LCA strictly above them” holds; for decreasing , the -th ancestors differ iff they are below the LCA, so jumping exactly then moves both to the highest pair of distinct ancestors, i.e. the children of the LCA on the two branches (any remaining gap would be a sum of smaller powers already tried).queryperforms the same moves and combines exactly the edges walked over.
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)]; }
};Proof: the preorder RMQ formula
Let with . If , is in ‘s subtree, and the positions all lie in ‘s subtree below , including the child of on the way to (it is entered before and after ); has the minimum depth , and every vertex of depth in this range is a child of , so the parent of the minimiser is . If , then and lie in different child subtrees of , the one of entered after the one of ; the range contains the child of above (entered between and ) and only vertices strictly inside ‘s subtree, so the minimum depth is and is attained at children of . Either way the parent of the minimiser is .
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;
}Proof: Tarjan's invariant
At any moment let be the DFS stack (root to current). Every visited vertex belongs to the set of the deepest stack vertex that is an ancestor of , and that set’s label is : a finished vertex is merged into its parent, whose label is the parent while the parent is on the stack, and when the parent finishes the merged set moves up again. When we enter and was visited earlier, the deepest stack vertex above is an ancestor of both, and no deeper common ancestor exists (the next stack vertex below it is not an ancestor of ). So it is . Each query is answered when its later endpoint is entered (the earlier one is then visited; equal endpoints give themselves).
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;
}
};Proof: Schieber–Vishkin
Runs. In an interval the number with the most trailing zeros is unique ( with the bits below the highest bit where and differ cleared). If is a child of , ‘s interval lies inside ‘s, so with equality iff , and at most one child shares . So equal- vertices form vertical paths (runs), and heights strictly increase from run to run going up; hence lists the run heights on ‘s root path and its lowest bit is ‘s own run height.
Embedding. In the complete binary tree on in-order numbers (node at height ), the subtree of a node of height is . With , ‘s interval contains no multiple of (it would have more trailing zeros), so it lies inside the subtree of , and so does for every descendant . Thus tree ancestors map to -ancestors, and is the height of the -LCA of (the standard formula: the highest differing bit, unless one is an ancestor of the other).
Height of the LCA’s run. Let . is a common -ancestor of , so its height is and appears in both . Suppose a common bit with exists: has an ancestor and an ancestor whose runs have height , so , are the -ancestors of , at height , which coincide. Then are on one vertical path, the shallower is a common ancestor of and , hence an ancestor of , and its height would be : contradiction. So (the lowest common bit ) is the height of ‘s run.
Entering. If ‘s own run has height , it is ‘s run. Otherwise the last run on ‘s root path below height has height = highest bit of below , and its -value is the -ancestor of at height , i.e. ‘s bits above , a one, then zeros; the parent of that run’s head is in the next run up, of height . So
enterreturns the deepest vertex of ‘s root path lying in ‘s run, and likewise for . Both are descendants of on one vertical path; the shallower is an ancestor of both and , hence equals .
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];
}
};Proof: ladders answer the remaining jump
After jumping from to , has a descendant levels below, so its height (vertices on the longest downward path) is , and the long path through continues downward along maximum heights for at least more vertices. Let that long path have top , length , and let be at index from the top; then . The ladder holds the path plus ancestors above . The remaining steps either stay on the path () or need ancestors above , which exist because . So the answer is in the ladder at index . Each ladder has at most twice its path’s length, so all ladders take entries.
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
}| Fact | Formula |
|---|---|
| LCA of a set | LCA 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 |
Proof: the path facts
Set LCA: every vertex of the set lies in the preorder interval between the min-
tinand max-tinvertices, and the LCA of those two has a subtree covering that whole interval, so it is a common ancestor; any common ancestor of the set is one of those two, so it is the deepest. Meeting point: among the three pairwise LCAs two coincide (the shallowest) and the third, , is the deepest; lies on all three pairwise paths, so , which is a lower bound for any point by the triangle inequality. Difference trick: a vertex’s final subtree sum gets for each of in its subtree, if the LCA is in it and if the LCA’s parent is; on the path this totals , and elsewhere. Intersection: a vertex is on both paths iff it is on the path between the deepest two cross LCAs, by examining where and attach to the path –; the special case of equal deepest values is a single vertex, which lies on both paths only if it is below both path LCAs.