guest@itl:~/home/data-structures$
templates/hld.cpp
compilable
$cat templates/hld

Heavy-Light Decomposition

Decomposes a tree into chains for O(log^2 n) path queries using a segment tree.

#hld#heavy-light#decomposition#path-query#tree
Log in to track progress, save custom versions, and organize into collections.Log In
$cat source_code/
cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 2e5 + 5;
vector<int> adj[MAXN];
int par[MAXN], dep[MAXN], sz[MAXN], heavy[MAXN];
int head[MAXN], pos[MAXN], timer;

void dfs(int u, int p, int d) {
    par[u] = p; dep[u] = d; sz[u] = 1; heavy[u] = -1;
    int mx = 0;
    for (int v : adj[u]) {
        if (v == p) continue;
        dfs(v, u, d + 1);
        sz[u] += sz[v];
        if (sz[v] > mx) { mx = sz[v]; heavy[u] = v; }
    }
}

void decompose(int u, int h) {
    head[u] = h; pos[u] = ++timer;
    if (heavy[u] != -1) decompose(heavy[u], h);
    for (int v : adj[u])
        if (v != par[u] && v != heavy[u]) decompose(v, v);
}

void build(int root) {
    timer = 0;
    dfs(root, -1, 0);
    decompose(root, root);
}

vector<pair<int,int>> pathQuery(int u, int v) {
    vector<pair<int,int>> segs;
    while (head[u] != head[v]) {
        if (dep[head[u]] < dep[head[v]]) swap(u, v);
        segs.push_back({pos[head[u]], pos[u]});
        u = par[head[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    segs.push_back({pos[u], pos[v]});
    return segs;
}

pair<int,int> subtreeRange(int u) {
    return {pos[u], pos[u] + sz[u] - 1};
}
47 linesutf-8
$cat explanation_notes.md
notes_viewer --renderedmarkdown (math enabled)

Heavy-Light Decomposition

Breaks a tree into chains so any root-to-leaf path crosses at most O(logn)O(\log n) light edges. Path queries split into O(logn)O(\log n) contiguous segments, each handled by a segment tree.

How It Works

  • DFS computes subtree sizes and identifies the heavy child (largest subtree)

  • Second DFS assigns linear positions: heavy children extend the current chain, light children start new chains

  • Path query (u,v)(u, v): repeatedly climb the deeper node to its chain head, querying the segment tree on each chain segment
  • Key Definitions

  • Heavy edge — connects node to its largest-subtree child

  • Light edge — all other edges. Crossing one at least doubles subtree size, bounding light edges per path to O(logn)O(\log n)
  • Operations

  • pathQuery(u, v) — returns list of [l,r][l, r] segment ranges to query

  • subtreeRange(u) — returns [pos[u],pos[u]+sz[u]1][\text{pos}[u], \text{pos}[u] + \text{sz}[u] - 1]
  • When to Use

  • Path sum/max/min queries on trees (with a segment tree)

  • Subtree queries

  • Edge or vertex values
  • Complexity

  • Build — O(n)O(n)

  • Path query — O(log2n)O(\log^2 n) with segment tree

  • Space — O(n)O(n)