Skip to content
DSA advanced 9 min read

Segment Trees: Fast Range Queries & Updates

A Segment Tree is a binary tree built over an array that lets you answer range queries (sum, min, max, gcd…) and apply updates in O(log n) each, even as the underlying values keep changing. A plain array gives O(1) updates but O(n) range sums; a prefix-sum array gives O(1) range sums but O(n) updates. The segment tree balances both at O(log n), which is why it’s the standard answer to “range sum query — mutable”.

The idea

Each node stores the aggregate (here, the sum) of a contiguous segment of the array. The root covers the whole array [0, n); each node splits its range in half between two children, down to leaves that cover a single element. Any query range can be assembled from O(log n) of these precomputed segments.

We store the tree in a flat array tree of size 2n using the simple convention: leaf i sits at tree[n + i], and the parent of node k is tree[k >> 1]. This iterative layout is compact and fast.

Building the tree

Copy the input into the leaves, then fill internal nodes bottom-up: each parent is the sum of its two children.

struct SegTree {
    int n;
    std::vector<long long> tree;
    SegTree(const std::vector<int>& a) : n(a.size()), tree(2 * a.size()) {
        for (int i = 0; i < n; i++) tree[n + i] = a[i];      // leaves
        for (int i = n - 1; i > 0; i--)                       // internal nodes
            tree[i] = tree[2 * i] + tree[2 * i + 1];
    }
};

Point update

To set a[i] = value, write the leaf, then walk up to the root recomputing each parent from its two children.

void update(int i, long long value) {
    i += n;
    tree[i] = value;
    for (i >>= 1; i >= 1; i >>= 1)
        tree[i] = tree[2 * i] + tree[2 * i + 1];
}

Range query

To sum [l, r) (left inclusive, right exclusive), start at the leaves and move up. Whenever a boundary is a right child, it lies fully inside the range — add it and step inward; same on the right end. This sweeps O(log n) segments.

long long query(int l, int r) {   // sum of [l, r)
    long long res = 0;
    for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
        if (l & 1) res += tree[l++];
        if (r & 1) res += tree[--r];
    }
    return res;
}

Going deeper: Swap + for min/max/gcd (and the identity element from 0 to +∞/-∞/0) and the same structure answers range-minimum, range-maximum, or range-gcd queries. For range updates (add a value to a whole range) you add lazy propagation on top.

Complexity

OperationTimeSpace
BuildO(n)O(n)
Point updateO(log n)
Range queryO(log n)

Common pitfalls

  • Half-open vs closed ranges. This implementation uses [l, r). To query the closed range [l, r], call query(l, r + 1). Pick one convention and stay consistent.
  • Overflow. Sums of many large ints overflow 32-bit types — use 64-bit accumulators (long long / long).
  • Forgetting to update ancestors. After writing a leaf you must recompute every ancestor, not just the immediate parent.

When to use it

Use a segment tree when values change and you need range aggregates: mutable range-sum/min/max queries, and as a building block for harder problems. If values never change, a prefix-sum array is simpler. If you only need prefix sums with point updates, a Fenwick tree is lighter and faster to code.

Practice on the advanced exercises.

Last updated June 25, 2026
Was this helpful?