Skip to content

Fenwick Tree (BIT)#

Metadata

Level: Intermediate · Difficulty: 3.0 · Complexity: O(log n) per operation

A Fenwick tree (or Binary Indexed Tree) maintains prefix sums of an array while allowing point updates and range queries, both in O(log n). It is the ideal structure when the array changes and we need range sums many times.

Each cell T[i] covers a block whose size is its lowest set bit
Each cell T[i] stores the sum of a block ending at position i whose size is the lowest set bit of i.

Idea#

A prefix-sum array answers queries in O(1), but changing a single element forces an O(n) rebuild of the prefixes. A Fenwick tree splits the information into blocks of different sizes so that both updating and querying touch only O(log n) blocks.

Internally the indices are 1-based (the user's 0-based position p lives at internal position p + 1). Cell T[i] stores the sum of the half-open range (i − lowbit(i), i], where lowbit(i) is the lowest set bit of i:

  • lowbit(i) = i & -i. In two's complement, -i flips every bit and adds 1, so i & -i keeps only the lowest set bit. That value is both the size of the block covered by T[i] and the jump we take while walking the tree.
  • Odd indices (lowbit = 1) cover a single element; powers of two cover a large block (T[8] covers all 8 positions).

update and query walk the tree in opposite directions:

  • update(index, delta) walks up: from the position, repeat index += index & -index. Each jump lands on the next cell whose block contains that position, so every affected block is updated (at most O(log n)).
  • query(index) walks down: repeat index -= index & -index. Removing the lowest set bit jumps to the block just before it; the visited blocks are disjoint and tile exactly [0, index). Since each step clears one bit, there are at most O(log n) steps.

Queries are half-open: query(index) returns the sum of [0, index), and a range sum is the difference of two prefixes: query_range(l, r) = query(r) − query(l).

Example#

Start from an array of 5 zeros and apply the test operations: update(0, 3) then update(2, 5). The logical array becomes a = [3, 0, 5, 0, 0].

After both updates the internal cells (1-based) hold:

Cell Covers (1-based positions) Value
T[1] {1} 3
T[2] {1, 2} 3
T[3] {3} 5
T[4] {1, 2, 3, 4} 8
T[5] {5} 0

Now query(3) (sum of [0, 3)) walks down from index = 3: add T[3] = 5, jump to 3 − 1 = 2, add T[2] = 3, jump to 0. Total 5 + 3 = 8. Note that the blocks (2, 3] and (0, 2] cover exactly the first three positions without overlapping.

Likewise query(5) adds T[5] = 0 and T[4] = 8, giving 8. Hence query_range(0, 3) = 8 and query_range(0, 5) = 8, the example's expected output.

Code#

/**
 * Fenwick Tree (Binary Indexed Tree).
 *
 * A structure for prefix sums with point updates: it stores an array and
 * answers range-sum queries and single-element updates, both in O(log n). Ideal
 * when the array keeps changing and range sums are needed many times.
 *
 * The public API is 0-based, but the tree is stored 1-based: cell `i` (1..n)
 * holds the sum of the half-open block (i - lowbit(i), i], where lowbit is the
 * lowest set bit of `i`. That block size is what lets both operations move in
 * O(log n) steps.
 *
 *   FenwickTree(n)        build an empty tree of n zeros            O(n)
 *   update(index, delta)  add `delta` at position `index`          O(log n)
 *   query(index)          -> prefix sum of [0, index)              O(log n)
 *   query_range(l, r)     -> sum of [l, r)                         O(log n)
 */
#include <vector>
using namespace std;

struct FenwickTree {
    int n;
    vector<int> tree;  // 1-based cell i (stored at tree[i-1]) covers (i-lowbit(i), i]

    // Create a Fenwick tree of `n` elements, all zero.
    FenwickTree(int n) : n(n), tree(n) {}

    // Add `delta` to the element at `index` (0-based).
    void update(int index, int delta) {
        // `index++` converts to 1-based, then we walk UP: `index & -index`
        // isolates the lowest set bit, and adding it jumps to the next cell
        // whose block also contains this position (at most O(log n) cells).
        for (index++; index <= n; index += index & -index)
            tree[index - 1] += delta;
    }

    // Return the prefix sum of [0, index).
    int query(int index) {
        // A 0-based exclusive bound equals the 1-based element count, so no +1.
        // Walk DOWN: subtracting the lowest set bit jumps to the block just
        // before this one; the visited blocks tile [0, index) with no overlap.
        int acc = 0;
        for (; index > 0; index -= index & -index)
            acc += tree[index - 1];
        return acc;
    }

    // Return the sum of the half-open range [l, r).
    int query_range(int l, int r) { return query(r) - query(l); }
};
#include <vector>
using namespace std;

struct FenwickTree {
    int n;
    vector<int> tree;

    FenwickTree(int n) : n(n), tree(n) {}

    void update(int index, int delta) {
        for (index++; index <= n; index += index & -index)
            tree[index - 1] += delta;
    }

    int query(int index) {
        int acc = 0;
        for (; index > 0; index -= index & -index)
            acc += tree[index - 1];
        return acc;
    }

    int query_range(int l, int r) { return query(r) - query(l); }
};
#include <vector>
using namespace std;
struct FenwickTree {
    int n;
    vector<int> tree;
    FenwickTree(int n) : n(n), tree(n) {}
    void update(int index, int delta) {
        for (index++; index <= n; index += index & -index)
            tree[index - 1] += delta;
    }
    int query(int index) {
        int acc = 0;
        for (; index > 0; index -= index & -index)
            acc += tree[index - 1];
        return acc;
    }
    int query_range(int l, int r) { return query(r) - query(l); }
};
"""Fenwick Tree (Binary Indexed Tree).

A structure for **prefix sums with point updates**: it stores an array and
answers range-sum queries and single-element updates, both in O(log n). Ideal
when the array keeps changing and range sums are needed many times.

The public API is 0-based, but the tree is stored 1-based: cell ``i`` (1..n)
holds the sum of the half-open block ``(i - lowbit(i), i]``, where ``lowbit`` is
the lowest set bit of ``i``. That block size is what lets both operations move
in O(log n) steps.

    build:  O(n)   ·  update: O(log n)  ·  query: O(log n)  ·  space: O(n)
"""


class FenwickTree:
    def __init__(self, n: int) -> None:
        """Create a Fenwick tree of ``n`` elements, all zero.

        Args:
            n (int): number of elements.
        """
        self.n: int = n
        self.tree: list[int] = [0] * n

    def update(self, index: int, delta: int) -> None:
        """Add ``delta`` to the element at ``index``.

        Args:
            index (int): 0-based position to update.
            delta (int): amount to add.
        """
        index += 1  # switch to 1-based indexing
        while index <= self.n:
            self.tree[index - 1] += delta
            # `index & -index` isolates the lowest set bit; adding it walks UP
            # to the next cell whose block also contains this position.
            index += index & -index

    def query(self, index: int) -> int:
        """Prefix sum of the half-open range ``[0, index)``.

        Args:
            index (int): exclusive upper bound.
        Returns:
            int: sum of the elements in ``[0, index)``.
        """
        # A 0-based exclusive bound equals the 1-based element count, so no +1.
        acc: int = 0
        while index > 0:
            acc += self.tree[index - 1]
            # Subtract the lowest set bit to walk DOWN to the previous block;
            # the visited blocks tile [0, index) with no overlap.
            index -= index & -index
        return acc

    def query_range(self, l: int, r: int) -> int:
        """Sum of the half-open range ``[l, r)``.

        Args:
            l (int): inclusive lower bound.
            r (int): exclusive upper bound.
        Returns:
            int: sum of the elements in ``[l, r)``.
        """
        return self.query(r) - self.query(l)
class FenwickTree:
    def __init__(self, n: int) -> None:
        self.n: int = n
        self.tree: list[int] = [0] * n

    def update(self, index: int, delta: int) -> None:
        index += 1
        while index <= self.n:
            self.tree[index - 1] += delta
            index += index & -index

    def query(self, index: int) -> int:
        acc: int = 0
        while index > 0:
            acc += self.tree[index - 1]
            index -= index & -index
        return acc

    def query_range(self, l: int, r: int) -> int:
        return self.query(r) - self.query(l)
class FenwickTree:
    def __init__(self, e):
        self.n = e
        self.tree = [0] * e
    def update(self, c, b):
        c += 1
        while c <= self.n:
            self.tree[c - 1] += b
            c += c & -c
    def query(self, c):
        a = 0
        while c > 0:
            a += self.tree[c - 1]
            c -= c & -c
        return a
    def query_range(self, d, f):
        return self.query(f) - self.query(d)

Complexity#

Operation Complexity Reason
Build O(n) allocate the vector of n zeros
update O(log n) walks up jumping block by block, one per bit
query O(log n) walks down adding disjoint blocks, one per set bit
Memory O(n) one cell per element

When to use it#

  • Changing array + many range sums → Fenwick tree. This is where it shines: both operations in O(log n) with very little code and a small constant.
  • Static array (never changes) → a plain precomputed prefix-sum array answers in O(1) and is simpler; you do not need a Fenwick tree.
  • More general operations (range max/min, range assignment, lower-bound searches) → a segment tree is more flexible at the cost of more code. The Fenwick tree is its lightweight version for sums.

Trick

For range updates with point queries, apply the Fenwick tree on the difference array: update(l, +v) and update(r, −v) add v to the whole [l, r).

Common pitfalls#

  • Mixing up 0-based (public interface) and 1-based (internal). The index++ at the start of update performs that conversion; do not duplicate it.
  • Forgetting that queries are half-open: query_range(l, r) excludes r. For the inclusive sum of [l, r] use query_range(l, r + 1).
  • Sizing the tree wrong: the loop in update checks index <= n, so the vector must have room for the n 1-based positions.

Practice#

Name Difficulty Link
fenwick 4.0 Kattis
supercomputer 2.7 Kattis

References#