Skip to main content

heavy_light_decomposition/
lib.rs

1//! Heavy-Light Decomposition (HLD).
2//!
3//! Heavy-Light Decomposition splits a tree into "heavy" paths (chains of
4//! vertices where each parent has its largest subtree as its first
5//! child) and "light" edges (connecting chains). Once decomposed, any
6//! path from `u` to `v` can be split into O(log n) segments, each lying
7//! within a single chain. By storing per-chain data in a Fenwick tree
8//! or segment tree, you can answer path queries / updates in
9//! O(log² n) overall.
10//!
11//! This crate ships the decomposition itself: it computes for each
12//! vertex its parent, depth, subtree size, the head of the chain it
13//! belongs to, and its position in the linearised chain order. You can
14//! then plug in any range-query data structure of your choice on top
15//! of that linearisation.
16//!
17//! # Example
18//!
19//! ```
20//! use heavy_light_decomposition::Hld;
21//!
22//! // Tree:
23//! //      0
24//! //      |
25//! //      1
26//! //     / \
27//! //    2   3
28//! //    |   |
29//! //    4   5
30//! let adj = vec![
31//!     vec![1],
32//!     vec![0, 2, 3],
33//!     vec![1, 4],
34//!     vec![1, 5],
35//!     vec![2],
36//!     vec![3],
37//! ];
38//! let hld = Hld::new(&adj, 0);
39//! assert_eq!(hld.parent[1], Some(0));
40//! assert_eq!(hld.depth[4], 3);
41//! ```
42
43/// Heavy-Light Decomposition over a rooted tree.
44#[derive(Debug, Clone)]
45pub struct Hld {
46    /// Parent of each vertex in the rooted tree. `None` for the root.
47    pub parent: Vec<Option<usize>>,
48    /// Depth of each vertex (root has depth 0).
49    pub depth: Vec<usize>,
50    /// Subtree size of each vertex (including itself).
51    pub size: Vec<usize>,
52    /// Head of the heavy chain containing each vertex.
53    pub head: Vec<usize>,
54    /// Position of each vertex in the linearised order of chains.
55    pub pos: Vec<usize>,
56}
57
58impl Hld {
59    /// Build the decomposition for the tree `adj` rooted at `root`.
60    ///
61    /// `adj[v]` is the list of neighbours of `v`. The tree is assumed
62    /// to be connected and undirected; edges to a vertex's parent are
63    /// filtered automatically.
64    ///
65    /// # Panics
66    /// Panics if `root >= adj.len()`.
67    pub fn new(adj: &[Vec<usize>], root: usize) -> Self {
68        let n = adj.len();
69        assert!(root < n, "root vertex out of range");
70
71        // Phase 1: parent + depth + subtree size via iterative DFS.
72        let mut parent: Vec<Option<usize>> = vec![None; n];
73        let mut depth = vec![0usize; n];
74        let mut size = vec![1usize; n];
75        // We store (vertex, parent, state) where state = 0 means "enter"
76        // and state = 1 means "exit".
77        let mut stack: Vec<(usize, Option<usize>, u8)> = vec![(root, None, 0)];
78        let mut order: Vec<usize> = Vec::with_capacity(n);
79        while let Some((v, p, state)) = stack.pop() {
80            if state == 0 {
81                parent[v] = p;
82                depth[v] = match p {
83                    Some(pv) => depth[pv] + 1,
84                    None => 0,
85                };
86                stack.push((v, p, 1));
87                for &u in &adj[v] {
88                    if Some(u) != p {
89                        stack.push((u, Some(v), 0));
90                    }
91                }
92            } else {
93                order.push(v);
94            }
95        }
96        for &v in &order {
97            if v != root {
98                if let Some(p) = parent[v] {
99                    size[p] += size[v];
100                }
101            }
102        }
103
104        // Phase 2: chain heads + positions via DFS from root, picking
105        // the heavy child as the next link.
106        let mut head = vec![root; n];
107        let mut pos = vec![0usize; n];
108        let mut cur_pos = 0usize;
109        dfs_decompose(adj, root, usize::MAX, root, &size, &mut head, &mut pos, &mut cur_pos);
110
111        Hld {
112            parent,
113            depth,
114            size,
115            head,
116            pos,
117        }
118    }
119
120    /// Decompose the path between `u` and `v` into half-open segments
121    /// `[lo, hi)` in the linearised chain order. Pass these segments to
122    /// a Fenwick / segment tree built on `pos` to answer path queries.
123    pub fn path_segments(&self, u: usize, v: usize) -> Vec<(usize, usize)> {
124        let mut segs = Vec::new();
125        let (mut u, mut v) = (u, v);
126        while self.head[u] != self.head[v] {
127            if self.depth[self.head[u]] > self.depth[self.head[v]] {
128                let h = self.head[u];
129                segs.push((self.pos[h], self.pos[u] + 1));
130                u = self.parent[h].unwrap();
131            } else {
132                let h = self.head[v];
133                segs.push((self.pos[h], self.pos[v] + 1));
134                v = self.parent[h].unwrap();
135            }
136        }
137        let (lo, hi) = if self.pos[u] <= self.pos[v] {
138            (self.pos[u], self.pos[v] + 1)
139        } else {
140            (self.pos[v], self.pos[u] + 1)
141        };
142        segs.push((lo, hi));
143        segs
144    }
145}
146
147fn dfs_decompose(
148    adj: &[Vec<usize>],
149    v: usize,
150    p: usize,
151    head: usize,
152    size: &[usize],
153    head_out: &mut [usize],
154    pos_out: &mut [usize],
155    cur_pos: &mut usize,
156) {
157    head_out[v] = head;
158    pos_out[v] = *cur_pos;
159    *cur_pos += 1;
160
161    // Find the heavy child (largest subtree).
162    let mut heavy: Option<usize> = None;
163    let mut heavy_size = 0usize;
164    for &u in &adj[v] {
165        if u != p && size[u] > heavy_size {
166            heavy_size = size[u];
167            heavy = Some(u);
168        }
169    }
170
171    // Recurse into the heavy child continuing the chain.
172    if let Some(h) = heavy {
173        dfs_decompose(adj, h, v, head, size, head_out, pos_out, cur_pos);
174    }
175
176    // Recurse into light children starting new chains.
177    for &u in &adj[v] {
178        if u != p && Some(u) != heavy {
179            dfs_decompose(adj, u, v, u, size, head_out, pos_out, cur_pos);
180        }
181    }
182}