Skip to main content

Fenwick Tree (Binary Indexed Tree)

Prefix sum queries, range sum updates, and advanced applications

Learn about the Fenwick Tree, a space-efficient data structure for prefix sum queries and range updates.

Fenwick Tree Fundamentals​

What is a Fenwick Tree?​

A Fenwick Tree (also called Binary Indexed Tree) is a data structure that efficiently supports prefix sum queries and point updates. It's more space-efficient than a Segment Tree for prefix sum operations.

Basic Structure​

class FenwickTree {
private:
vector<int> tree;
int n;

public:
FenwickTree(int size) {
n = size;
tree.resize(n + 1, 0);
}

// Get the least significant bit
int lsb(int x) {
return x & (-x);
}

// Update value at index idx
void update(int idx, int delta) {
idx++; // 1-indexed
while (idx <= n) {
tree[idx] += delta;
idx += lsb(idx);
}
}

// Query prefix sum from 1 to idx
int query(int idx) {
idx++; // 1-indexed
int sum = 0;
while (idx > 0) {
sum += tree[idx];
idx -= lsb(idx);
}
return sum;
}

// Query range sum from l to r
int rangeQuery(int l, int r) {
return query(r) - query(l - 1);
}
};

Prefix Sum Queries​

Basic Prefix Sum​

// Get sum of elements from index 0 to idx
int prefixSum(int idx) {
idx++; // Convert to 1-indexed
int sum = 0;
while (idx > 0) {
sum += tree[idx];
idx -= lsb(idx);
}
return sum;
}

Range Sum Query​

// Get sum of elements from index l to r
int rangeSum(int l, int r) {
return prefixSum(r) - prefixSum(l - 1);
}

Point Update​

// Add delta to element at index idx
void pointUpdate(int idx, int delta) {
idx++; // Convert to 1-indexed
while (idx <= n) {
tree[idx] += delta;
idx += lsb(idx);
}
}

Range Sum Updates​

Range Update with Point Query​

class RangeUpdateFenwickTree {
private:
FenwickTree tree1, tree2;
int n;

public:
RangeUpdateFenwickTree(int size) : tree1(size), tree2(size), n(size) {}

// Update range [l, r] by adding val
void rangeUpdate(int l, int r, int val) {
tree1.update(l, val);
tree1.update(r + 1, -val);
tree2.update(l, val * l);
tree2.update(r + 1, -val * (r + 1));
}

// Get value at index idx
int pointQuery(int idx) {
return tree1.query(idx) * (idx + 1) - tree2.query(idx);
}

// Get sum from 0 to idx
int prefixSum(int idx) {
return tree1.query(idx) * (idx + 1) - tree2.query(idx);
}

// Get range sum from l to r
int rangeSum(int l, int r) {
return prefixSum(r) - prefixSum(l - 1);
}
};

Range Update with Range Query​

class RangeUpdateRangeQueryFenwickTree {
private:
FenwickTree tree1, tree2;
int n;

public:
RangeUpdateRangeQueryFenwickTree(int size) : tree1(size), tree2(size), n(size) {}

// Update range [l, r] by adding val
void rangeUpdate(int l, int r, int val) {
tree1.update(l, val);
tree1.update(r + 1, -val);
tree2.update(l, val * l);
tree2.update(r + 1, -val * (r + 1));
}

// Get sum from 0 to idx
int prefixSum(int idx) {
return tree1.query(idx) * (idx + 1) - tree2.query(idx);
}

// Get range sum from l to r
int rangeSum(int l, int r) {
return prefixSum(r) - prefixSum(l - 1);
}
};

Inversion Count​

Count Inversions in Array​

// Count number of inversions in array
int countInversions(vector<int>& arr) {
int n = arr.size();

// Coordinate compression
vector<int> temp = arr;
sort(temp.begin(), temp.end());

for (int i = 0; i < n; i++) {
arr[i] = lower_bound(temp.begin(), temp.end(), arr[i]) - temp.begin() + 1;
}

// Count inversions using Fenwick Tree
FenwickTree ft(n);
int inversions = 0;

for (int i = n - 1; i >= 0; i--) {
inversions += ft.query(arr[i] - 1);
ft.update(arr[i], 1);
}

return inversions;
}

Count Inversions in 2D​

// Count inversions in 2D array
int countInversions2D(vector<pair<int, int>>& points) {
int n = points.size();

// Sort by x-coordinate
sort(points.begin(), points.end());

// Coordinate compression for y-coordinates
vector<int> yCoords;
for (auto& p : points) {
yCoords.push_back(p.second);
}
sort(yCoords.begin(), yCoords.end());

for (auto& p : points) {
p.second = lower_bound(yCoords.begin(), yCoords.end(), p.second) - yCoords.begin() + 1;
}

// Count inversions
FenwickTree ft(n);
int inversions = 0;

for (int i = n - 1; i >= 0; i--) {
inversions += ft.query(points[i].second - 1);
ft.update(points[i].second, 1);
}

return inversions;
}

2D Fenwick Tree​

2D Range Sum Query​

class FenwickTree2D {
private:
vector<vector<int>> tree;
int n, m;

public:
FenwickTree2D(int rows, int cols) {
n = rows;
m = cols;
tree.resize(n + 1, vector<int>(m + 1, 0));
}

// Update value at (row, col)
void update(int row, int col, int delta) {
row++; col++; // 1-indexed
for (int i = row; i <= n; i += lsb(i)) {
for (int j = col; j <= m; j += lsb(j)) {
tree[i][j] += delta;
}
}
}

// Query sum from (1,1) to (row, col)
int query(int row, int col) {
row++; col++; // 1-indexed
int sum = 0;
for (int i = row; i > 0; i -= lsb(i)) {
for (int j = col; j > 0; j -= lsb(j)) {
sum += tree[i][j];
}
}
return sum;
}

// Query sum in rectangle from (r1, c1) to (r2, c2)
int rangeQuery(int r1, int c1, int r2, int c2) {
return query(r2, c2) - query(r1 - 1, c2) - query(r2, c1 - 1) + query(r1 - 1, c1 - 1);
}

private:
int lsb(int x) {
return x & (-x);
}
};

Advanced Applications​

Kth Smallest Element​

// Find kth smallest element in array
int kthSmallest(vector<int>& arr, int k) {
int n = arr.size();

// Coordinate compression
vector<int> temp = arr;
sort(temp.begin(), temp.end());

for (int i = 0; i < n; i++) {
arr[i] = lower_bound(temp.begin(), temp.end(), arr[i]) - temp.begin() + 1;
}

// Binary search for kth smallest
FenwickTree ft(n);
int left = 1, right = n;

while (left < right) {
int mid = (left + right) / 2;
int count = ft.query(mid);

if (count < k) {
left = mid + 1;
} else {
right = mid;
}
}

return temp[left - 1];
}

Range Sum with Updates​

class RangeSumFenwickTree {
private:
FenwickTree tree;
vector<int> arr;
int n;

public:
RangeSumFenwickTree(vector<int>& input) : tree(input.size()), arr(input), n(input.size()) {
for (int i = 0; i < n; i++) {
tree.update(i, arr[i]);
}
}

// Update value at index idx
void update(int idx, int newVal) {
int delta = newVal - arr[idx];
arr[idx] = newVal;
tree.update(idx, delta);
}

// Get sum from index l to r
int rangeSum(int l, int r) {
return tree.rangeQuery(l, r);
}

// Get value at index idx
int get(int idx) {
return arr[idx];
}
};

Frequency Array Operations​

class FrequencyFenwickTree {
private:
FenwickTree tree;
int maxVal;

public:
FrequencyFenwickTree(int maxValue) : tree(maxValue), maxVal(maxValue) {}

// Add value to frequency array
void add(int val) {
tree.update(val, 1);
}

// Remove value from frequency array
void remove(int val) {
tree.update(val, -1);
}

// Get frequency of value
int getFrequency(int val) {
return tree.rangeQuery(val, val);
}

// Get count of values <= val
int getCountLessEqual(int val) {
return tree.query(val);
}

// Get count of values < val
int getCountLess(int val) {
return tree.query(val - 1);
}

// Get count of values > val
int getCountGreater(int val) {
return tree.query(maxVal) - tree.query(val);
}
};

Performance Analysis​

Time Complexity​

  • Update: O(log n)
  • Query: O(log n)
  • Range Update: O(log n)
  • Range Query: O(log n)
  • Build: O(n log n)

Space Complexity​

  • 1D Fenwick Tree: O(n)
  • 2D Fenwick Tree: O(n × m)
  • Range Update Tree: O(n)

Advantages over Segment Tree​

  • Space efficient: O(n) vs O(4n)
  • Simple implementation: Less code
  • Cache friendly: Better memory access pattern
  • Fast for prefix queries: Optimized for prefix operations

Common Patterns​

  1. Prefix sum queries with point updates
  2. Range sum queries with point updates
  3. Range updates with point queries
  4. Inversion counting in arrays
  5. Frequency array operations

Applications​

  • Range sum queries: Efficient prefix sum operations
  • Inversion counting: Count inversions in arrays
  • 2D range queries: Matrix range operations
  • Frequency problems: Count frequencies efficiently
  • Competitive programming: Fast range operations

When to Use Fenwick Tree​

  • Prefix sum queries are frequent
  • Point updates are common
  • Space is limited compared to Segment Tree
  • Simple implementation is preferred
  • Range queries are less frequent than prefix queries