Fenwick Tree (Binary Indexed Tree)

Back Home

Category

Category: Trees

Topics

Topics: prefix sums, point updates, competitive programming, inversion counting

Definition

A data structure that efficiently maintains prefix sums of an array, supporting both point updates and prefix queries in O(log n) time using clever bit manipulation.

Use cases

Attributes

Common operations

Operation Time Description
Build O(n) Initialize from array
Point update O(log n) Add delta to element
Prefix sum O(log n) Sum of elements [0, i]
Range sum O(log n) Sum of elements [l, r]

In code

class FenwickTree:
    def __init__(self, n):
        """Initialize with size n (1-indexed internally)"""
        self.n = n
        self.tree = [0] * (n + 1)

    @classmethod
    def from_array(cls, arr):
        """Build from existing array - O(n)"""
        n = len(arr)
        ft = cls(n)
        for i, val in enumerate(arr):
            ft.update(i, val)
        return ft

    def update(self, idx, delta):
        """Add delta to element at idx (0-indexed) - O(log n)"""
        idx += 1  # Convert to 1-indexed
        while idx <= self.n:
            self.tree[idx] += delta
            idx += idx & (-idx)  # Add lowest set bit

    def prefix_sum(self, idx):
        """Sum of elements [0, idx] (0-indexed) - O(log n)"""
        idx += 1  # Convert to 1-indexed
        total = 0
        while idx > 0:
            total += self.tree[idx]
            idx -= idx & (-idx)  # Remove lowest set bit
        return total

    def range_sum(self, left, right):
        """Sum of elements [left, right] (0-indexed) - O(log n)"""
        if left == 0:
            return self.prefix_sum(right)
        return self.prefix_sum(right) - self.prefix_sum(left - 1)

# Usage
arr = [1, 3, 5, 7, 9, 11]
ft = FenwickTree.from_array(arr)

ft.prefix_sum(3)      # Sum of arr[0:4] = 1+3+5+7 = 16
ft.range_sum(1, 4)    # Sum of arr[1:5] = 3+5+7+9 = 24
ft.update(2, 5)       # arr[2] += 5 (now arr[2] = 10)
ft.prefix_sum(3)      # Now = 1+3+10+7 = 21

# Bit manipulation explanation:
# idx & (-idx) extracts the lowest set bit
# Example: 12 = 1100 binary
#         -12 = 0100 (two's complement)
#  12 & (-12) = 0100 = 4

# Counting inversions using Fenwick Tree
def count_inversions(arr):
    """Count pairs (i, j) where i < j and arr[i] > arr[j]"""
    # Coordinate compression
    sorted_arr = sorted(set(arr))
    rank = {v: i for i, v in enumerate(sorted_arr)}

    n = len(sorted_arr)
    ft = FenwickTree(n)
    inversions = 0

    for i in range(len(arr) - 1, -1, -1):
        r = rank[arr[i]]
        inversions += ft.prefix_sum(r - 1) if r > 0 else 0
        ft.update(r, 1)

    return inversions

Time complexity

Space complexity

O(n) - just an array of size n+1. No pointers or additional structures. More space-efficient than segment tree.

Trade-offs

Pros:

Cons:

Variants

When to use vs avoid