Segment Trees & BIT (Fenwick Tree) — Complete Guide

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

Why Range Query Structures?

  • Naive: O(n) per query, O(1) update
  • Prefix sums: O(1) query, O(n) update
  • Segment Tree / BIT: O(log n) both query and update

Binary Indexed Tree (Fenwick Tree)

The simplest structure for prefix sum queries with point updates.

BIT Operations

class BIT:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (n + 1)

    def update(self, i, delta):
        while i <= self.n:
            self.tree[i] += delta
            i += i & (-i)  # move to parent

    def query(self, i):  # prefix sum [1..i]
        s = 0
        while i > 0:
            s += self.tree[i]
            i -= i & (-i)  # move to left sibling
        return s

    def range_query(self, l, r):
        return self.query(r) - self.query(l - 1)

BIT Trick: i & (-i) isolates lowest set bit

  • Update: add to i, then jump to next by adding i & (-i)
  • Query: sum from i, then jump to left by subtracting i & (-i)

Segment Tree

More powerful: supports any associative operation (min, max, GCD, etc.) and range updates with lazy propagation.

Simple Segment Tree

class SegTree:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (4 * n)

    def build(self, arr, node, start, end):
        if start == end:
            self.tree[node] = arr[start]
        else:
            mid = (start + end) // 2
            self.build(arr, 2*node, start, mid)
            self.build(arr, 2*node+1, mid+1, end)
            self.tree[node] = self.tree[2*node] + self.tree[2*node+1]

    def update(self, node, start, end, idx, val):
        if start == end:
            self.tree[node] = val
        else:
            mid = (start + end) // 2
            if idx <= mid:
                self.update(2*node, start, mid, idx, val)
            else:
                self.update(2*node+1, mid+1, end, idx, val)
            self.tree[node] = self.tree[2*node] + self.tree[2*node+1]

    def query(self, node, start, end, l, r):
        if r < start or end < l: return 0  # out of range
        if l <= start and end <= r: return self.tree[node]  # complete overlap
        mid = (start + end) // 2
        return self.query(2*node, start, mid, l, r) +                self.query(2*node+1, mid+1, end, l, r)

Lazy Propagation (Range Updates)

# Mark pending update at node; push down before querying children
def push_down(self, node, start, end):
    if self.lazy[node] != 0:
        mid = (start + end) // 2
        self.tree[2*node] += self.lazy[node] * (mid - start + 1)
        self.tree[2*node+1] += self.lazy[node] * (end - mid)
        self.lazy[2*node] += self.lazy[node]
        self.lazy[2*node+1] += self.lazy[node]
        self.lazy[node] = 0

Complexity

StructureBuildPoint UpdateRange QueryRange Update
BITO(n)O(log n)O(log n)
Segment TreeO(n)O(log n)O(log n)O(log n) lazy

Problem Index

#ProblemStructureDifficulty
01Range Sum Query MutableBIT / Seg TreeMedium
02Count of Smaller Numbers After SelfBIT / Merge SortHard
03Count of Range SumMerge Sort / BITHard
04Queue Reconstruction (revisit)BIT orderMedium
05Reverse PairsBIT / Merge SortHard
06Range Sum Query 2D Mutable2D BITHard
07My Calendar I, II, IIISegment Tree / SortedMedium/Hard
08Falling SquaresCoordinate compress + SegTreeHard
09Rectangle Area IICoordinate compressHard
10Number of Longest Increasing SubsequenceSegment Tree on LISMedium
11Max Sum of Rectangle No Larger Than KBIT + prefixHard
12Longest Increasing Subsequence (Seg Tree)SegTree on valueMedium
13Shifting Letters IIDifference Array + BITMedium
14Stamping the GridPrefix sums 2DHard
15Segment Tree Master RecapCheatsheet

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro