Segment Tree
A segment tree is a tree data structure built on top of an array that lets you answer range queries (like "what is the sum of elements from index 3 to 8?") and perform point updates in \(O(\log n)\) time each.
Without a segment tree, a range sum query takes \(O(n)\) time (loop through all elements). A prefix sum array gives \(O(1)\) queries but \(O(n)\) updates. With a segment tree, you preprocess the array in \(O(n)\) time and then answer any query in \(O(\log n)\) — a massive win when you have many queries.
The core idea: divide and conquer
A segment tree stores aggregated values for ranges. The root stores the aggregate for the entire array. Each internal node stores the aggregate for its half of the parent's range. Leaves store individual array elements.
For an array of size 8: [1, 3, 5, 7, 9, 11, 13, 15]
To query sum(2, 6): follow the tree, combining only the segments that overlap your range. At most 2 nodes per level are visited → \(O(\log n)\).
← → arrow keys to step · click elements to interact
Segment Tree: Build & Range Sum Query
Building the tree
Build bottom-up: place array values at leaves, then each internal node = combine(left child, right child). For a sum segment tree, each node = sum of its children. This takes \(O(n)\) time.
Querying a range
To query [l, r]: start at root. If current segment is fully inside [l, r], return its value. If fully outside, return the identity element (0 for sum). Otherwise, recurse into both children and combine results.
Point update
To update arr[i] = val: update the leaf, then walk up to the root updating each ancestor. \(O(\log n)\) nodes touched.
Lazy propagation
For range updates (add 5 to all elements from index 3 to 8), a plain segment tree takes \(O(n)\) time. Lazy propagation defers updates — store a pending update at each node and push it down only when the child is needed. This reduces range updates to \(O(\log n)\) as well.
Array vs. pointer implementation
Segment trees are commonly stored in an array of size 4n. Node i has children at 2i and 2i+1, parent at i//2. This avoids pointer overhead and is cache-friendly.