The LCA of and in a rooted tree is the deepest vertex that is an ancestor of both. It is the single most reused tree primitive — path queries, distances, and virtual trees all rest on it.
Method comparison
| Method | Preprocess | Query | Notes |
|---|---|---|---|
| Naive climbing | fine for tiny trees | ||
| Binary lifting | the default — also gives -th ancestor and path aggregates | ||
| Euler tour + sparse table | best constant when you only need LCA | ||
| Tarjan offline | amortized | needs all queries up front | |
| Farach-Colton-Bender | optimal, large constant | ||
| HLD | free if you already built HLD | ||
| Link-cut tree | am. | when the tree changes |
Binary lifting
const int LOG = 20;
vector<array<int, LOG>> up;
vector<int> depth;
void dfs(int u, int p) {
up[u][0] = (p == -1 ? u : p);
for (int k = 1; k < LOG; k++) up[u][k] = up[up[u][k-1]][k-1];
for (int v : adj[u]) if (v != p) { depth[v] = depth[u] + 1; dfs(v, u); }
}
int kthAncestor(int u, int k) {
for (int i = 0; i < LOG; i++) if (k >> i & 1) u = up[u][i];
return u;
}
int lca(int u, int v) {
if (depth[u] < depth[v]) swap(u, v);
u = kthAncestor(u, depth[u] - depth[v]);
if (u == v) return u;
for (int k = LOG - 1; k >= 0; k--)
if (up[u][k] != up[v][k]) { u = up[u][k]; v = up[v][k]; }
return up[u][0];
}The jump loop goes downward in and moves only when the ancestors differ — that lands both pointers on the children of the LCA. Jumping when they are equal would overshoot.
Euler tour + sparse table — queries
Record the depth at each of the visits of an Euler tour. is the minimum-depth entry between the first occurrences of and — a range minimum query.
vector<int> euler, firstOcc, dep;
void dfs(int u, int p, int d) {
firstOcc[u] = euler.size();
euler.push_back(u); dep.push_back(d);
for (int v : adj[u]) if (v != p) {
dfs(v, u, d + 1);
euler.push_back(u); dep.push_back(d); // re-visit on return
}
}
// then a sparse table over dep[], returning the index of the minimumShorter to write than it looks, and the constant factor beats binary lifting when is large.
What LCA unlocks
| Task | Formula |
|---|---|
| Distance | |
| Is an ancestor of ? | and — no LCA needed |
| Is on the path ? | |
| Path max/min/sum | binary lifting with an aggregate array, or HLD |
| Path updates | HLD + lazy segment tree, or difference-on-tree |
| -th vertex on a path | jump from , or from |
| Meeting point of three vertices | the vertex among that is deepest — exactly two of the three coincide |
| Virtual tree | sort by tin, insert consecutive LCAs |
Path updates without HLD
For additive path updates with a single query at the end, skip HLD entirely: add at and , and at and at , then take subtree sums in one DFS. per update, to finalise. This “difference array on a tree” trick is far simpler than HLD and covers a surprising number of problems.
See also: Binary Lifting · Euler Tour · HLD