Skip to content

Binary Indexed Tree

binary-indexed-tree: The Ultimate Cheatsheet

Section titled “binary-indexed-tree: The Ultimate Cheatsheet”

A Binary Indexed Tree (BIT), also known as a Fenwick tree, is a data structure that allows for efficient prefix sum queries and updates on an array. Unlike a simple array, which requires O(n) time for prefix sum calculation, a BIT achieves this in O(log n) time. This efficiency comes from cleverly representing the array’s prefix sums in a tree-like structure using bit manipulation.

Why is it important? BITs are crucial for solving problems involving frequent updates and prefix sum queries on a sequence of numbers. This makes them particularly useful in competitive programming and scenarios where performance is critical.

Core Concepts:

  • Prefix Sum: The sum of all elements from the beginning of an array up to a given index.
  • Binary Representation: The BIT leverages the binary representation of indices to efficiently calculate and update prefix sums. Each node in the implicit tree structure represents the sum of a specific range of elements determined by its index’s least significant bit.
  • LSB (Least Significant Bit): The rightmost 1 in the binary representation of a number. This is crucial for navigating the BIT’s structure.
  • Implicit Tree: The BIT doesn’t explicitly store the tree structure; instead, it uses an array to represent it implicitly. The relationships between nodes are determined through bit manipulation.

2. When to Use binary-indexed-tree (and When Not To)

Section titled “2. When to Use binary-indexed-tree (and When Not To)”

Use BIT when:

  • You need to perform frequent updates (element changes) and prefix sum queries on an array.
  • The updates and queries are on ranges that start from the beginning of the array (prefix sums). While BIT can be adapted for range updates/queries, other structures like segment trees are often better suited.
  • The size of the array is relatively large, making O(n) prefix sum calculations inefficient.

Don’t use BIT when:

  • You need to perform range sum queries (summing elements within a sub-array not starting from index 0). Segment trees are a more appropriate choice in this case.
  • You need to perform arbitrary range updates (updating a sub-array, not just a single element). Again, segment trees are generally preferred.
  • The number of updates and queries is small, making the overhead of BIT unnecessary. A simple array with prefix sums pre-computed might suffice.

3. Core Algorithm / Data Structure Template

Section titled “3. Core Algorithm / Data Structure Template”

Update (add val to element at index idx):

  1. While idx is within the array bounds:
    • Add val to the element at index idx in the BIT array.
    • Add the least significant bit of idx to idx (effectively moving up the implicit tree).

Query (get prefix sum up to index idx):

  1. Initialize sum to 0.
  2. While idx is greater than 0:
    • Add the element at index idx in the BIT array to sum.
    • Subtract the least significant bit of idx from idx (effectively moving down the implicit tree).
  3. Return sum.
class BinaryIndexedTree:
def __init__(self, n):
self.bit = [0] * (n + 1) # 1-based indexing for simplicity
def update(self, idx, val):
idx += 1 # Adjust for 1-based indexing
while idx < len(self.bit):
self.bit[idx] += val
idx += idx & -idx # Add LSB
def query(self, idx):
idx += 1 # Adjust for 1-based indexing
sum = 0
while idx > 0:
sum += self.bit[idx]
idx -= idx & -idx # Subtract LSB
return sum
class BinaryIndexedTree {
private int[] bit;
public BinaryIndexedTree(int n) {
bit = new int[n + 1]; // 1-based indexing
}
public void update(int idx, int val) {
idx++; // Adjust for 1-based indexing
while (idx < bit.length) {
bit[idx] += val;
idx += idx & -idx; // Add LSB
}
}
public int query(int idx) {
idx++; // Adjust for 1-based indexing
int sum = 0;
while (idx > 0) {
sum += bit[idx];
idx -= idx & -idx; // Subtract LSB
}
return sum;
}
}
#include <vector>
class BinaryIndexedTree {
public:
BinaryIndexedTree(int n) : bit(n + 1, 0) {} // 1-based indexing
void update(int idx, int val) {
idx++; // Adjust for 1-based indexing
while (idx < bit.size()) {
bit[idx] += val;
idx += idx & -idx; // Add LSB
}
}
int query(int idx) {
idx++; // Adjust for 1-based indexing
int sum = 0;
while (idx > 0) {
sum += bit[idx];
idx -= idx & -idx; // Subtract LSB
}
return sum;
}
private:
std::vector<int> bit;
};
OperationTime ComplexitySpace Complexity
UpdateO(log n)O(n)
QueryO(log n)O(n)

The space complexity is O(n) because we need an array of size n to store the BIT. Both update and query operations have a logarithmic time complexity because the number of iterations in the while loops is proportional to the number of bits in the index, which is O(log n). There are no best, average, or worst-case distinctions; the performance is consistently logarithmic.

  • 1-based Indexing: Using 1-based indexing simplifies the code by avoiding special handling for index 0. Remember to adjust indices accordingly (add 1 before using in BIT, subtract 1 after retrieving from BIT).
  • LSB Calculation: Understanding how idx & -idx efficiently calculates the LSB is key.
  • Off-by-one Errors: Carefully handle index adjustments for 1-based indexing to avoid off-by-one errors. Always double-check your boundary conditions.
  • Multiple Updates: You can perform multiple updates efficiently by batching them together. Instead of calling update multiple times, accumulate the changes and call update once.
  • Debugging: Print the BIT array at various stages to help debug issues.

The Skyline problem is not directly solvable using a single BIT in its standard form. BITs are best for prefix sum queries and single-element updates. The Skyline Problem requires handling multiple intervals and merging overlapping segments, making it more suitable for a sweep line algorithm combined with a priority queue or a segment tree. A BIT alone wouldn’t efficiently handle the merging of overlapping building heights. While you could potentially adapt a BIT to work with a more complex approach, it would be less efficient and less elegant than using a different data structure.