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

MethodPreprocessQueryNotes
Naive climbingfine for tiny trees
Binary liftingthe default — also gives -th ancestor and path aggregates
Euler tour + sparse tablebest constant when you only need LCA
Tarjan offlineamortizedneeds all queries up front
Farach-Colton-Benderoptimal, large constant
HLDfree 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 minimum

Shorter to write than it looks, and the constant factor beats binary lifting when is large.

What LCA unlocks

TaskFormula
Distance
Is an ancestor of ? and — no LCA needed
Is on the path ?
Path max/min/sumbinary lifting with an aggregate array, or HLD
Path updatesHLD + lazy segment tree, or difference-on-tree
-th vertex on a pathjump from , or from
Meeting point of three verticesthe vertex among that is deepest — exactly two of the three coincide
Virtual treesort 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