Fenwick Tree (Binary Indexed Tree)
Key Points
- β’A Fenwick Tree (Binary Indexed Tree) maintains prefix sums so you can update a single position and query a prefix in O( n) time with a tiny constant factor.
- β’It stores partial sums at indices determined by the lowest set bit, so bit[i] covers the range [i - lowbit(i) + 1, i].
- β’Prefix queries walk left by repeatedly subtracting lowbit(i); updates walk right by repeatedly adding lowbit(i).
- β’Range sums are computed as prefix(r) β prefix(l β 1), which also runs in O( n).
- β’You can extend it to support range updates with point queries using a single tree and to range updates with range queries using two trees.
- β’The structure is 1-indexed, which simplifies lowbit manipulations and implementation details.
- β’It supports order-statistics (find k-th element by prefix sum) in O( n) if all frequencies are nonnegative.
- β’Space usage is O(n) and it can be built in O(n) using the linear build trick; otherwise O(n n) by adding each element.
Prerequisites
- βArrays and Indexing (0-based vs 1-based) β Fenwick Tree is implemented as a 1-indexed array; understanding index mapping avoids off-by-one errors.
- βPrefix Sums β Fenwick Tree accelerates prefix sums; knowing naive prefix sums clarifies why the structure helps.
- βBinary Representation and Bitwise Operations β The lowbit function i & -i is central to navigating the tree efficiently.
- βAsymptotic Complexity (Big-O) β To evaluate when BIT is preferable to alternatives like segment trees.
- βBasic Algebra and Summations β Understanding how range sums decompose into prefixes and difference arrays.
- βCoordinate Compression (optional) β Useful when values are large but you need a frequency Fenwick Tree.
Detailed Explanation
Tap terms for definitions01Overview
Hook: Imagine keeping a running total of your daily study minutes. You want to quickly know how many minutes you've studied up to any day, and also be able to correct a single day's entry. Doing this naively would either make updates slow or queries slow. Concept: The Fenwick Tree (Binary Indexed Tree) is a compact data structure that balances bothβsupporting point updates and prefix-sum queries in O(\log n) time with minimal overhead. It leverages binary representation of indices to store cleverly chosen partial sums. Example: If you need the sum from day 1 to day 1000, you jump over blocks that are powers of two in size, summing a handful of values instead of all 1000. Similarly, to adjust day 27's minutes, you touch a handful of block sums that include day 27. This strategy gives very fast performance in practice and fits naturally into competitive programming problems where you need dynamic array sums, difference arrays, or order-statistics over frequencies.
02Intuition & Analogies
Think of a bookshelf where each shelf represents a day and the number of books is the value for that day. You want two superpowers: quickly count total books from the first shelf up to shelf i (a prefix), and quickly add or remove books from a single shelf (a point update). If you kept only the raw counts, prefixes would be slow (youβd add many shelves). If you kept a full prefix array, updates would be slow (youβd need to fix many entries). A Fenwick Tree stores pre-packed bundles of books: some shelves hold not just their own count but also the sums of several previous shelves. Which bundles are stored is determined by the lowest set bit in the index (lowbit). For example, at position 12 (binary 1100), lowbit(12) = 4; the cell for 12 stores the sum of shelves 9 through 12. To get prefix(12), you take cell 12 (sum of 9..12), then jump left by 4 (12 β 4 = 8), take cell 8 (sum of 1..8), then jump left by 8 (8 β 8 = 0) and stop. You visited just two cells to cover 12 shelves. When you update shelf 9, you move right by lowbit at each step: update 9, then 10, then 12, then 16, and so onβtouching exactly the bundles that include shelf 9. Because each jump either halves or doubles the covered interval, both operations take O(\log n) steps and are very fast in practice.
03Formal Definition
04When to Use
Use a Fenwick Tree when you need dynamic prefix sums with fast point updates: classic range-sum queries on mutable arrays, frequency counting, or maintaining cumulative counts. It shines in competitive programming for problems like: counting inversions (update a frequency at a value and query how many greater have appeared), dynamic range sums (query sum in [l, r] as prefix(r) β prefix(l β 1)), and maintaining histograms. With light extensions: (1) Range update + point query can be done by storing a difference array in one Fenwick Tree (add v at l and βv at r + 1; point value at x is prefix(x)). (2) Range update + range query can be done with two Fenwick Trees storing carefully crafted sequences so that prefix sums combine to produce updated ranges. (3) Order-statistics (k-th element) can be supported if all frequencies are nonnegative by binary lifting on the tree to locate the smallest index with cumulative frequency β₯ k. Prefer Fenwick Trees over Segment Trees when operations are limited to sums and you care about low constants, memory simplicity, or straightforward implementation.
β οΈCommon Mistakes
- Off-by-one indexing: Fenwick Trees are almost always 1-indexed. Forgetting to map array indices (0-based) to 1-based causes wrong sums or out-of-bounds. Always use i + 1 when building from a 0-based vector. 2) Wrong r + 1 update in range tricks: For range updates you must perform the negative update at r + 1, and only if r + 1 β€ n. Missing this condition corrupts sums. 3) Overflow: Prefix sums can exceed 32-bit range; use long long (64-bit) for sums and deltas. 4) Using k-th on arbitrary signed updates: The k-th order-statistic search assumes nonnegative frequencies and monotone cumulative sums. Negative values break the search logic. 5) Forgetting to clear between test cases: In contests with multiple test cases, re-initialize bit to zeros, or reuse a constructor. 6) Mixing inclusive/exclusive ranges: Remember range sums are inclusive on both ends, implemented as prefix(r) β prefix(l β 1). 7) Linear build vs. O(n log n) build: When given an initial array, use the O(n) build trick; repeatedly calling add for each element is slower. 8) Using 0 as an index: Since lowbit(0) = 0, loops like while (i > 0) are essential; if you ever start with i = 0, the loop stalls.
Key Formulas
Lowbit
Explanation: The lowest set bit of i, i.e., the largest power of two dividing i. It determines the block size stored at index i in the BIT.
BIT Cell Meaning
Explanation: Each cell i stores the sum over a range ending at i of length equal to lowbit(i). This is why prefix queries can skip blocks.
Prefix Decomposition
Explanation: The prefix sum up to k is obtained by summing selected BIT cells P(k) found by repeatedly subtracting lowbit. Each step jumps to a disjoint earlier block.
Point Update Propagation
Explanation: Updating a single position affects all BIT cells whose stored range includes p. These indices are reached by repeatedly adding lowbit.
Range Sum from Prefixes
Explanation: The sum over [l, r] equals the prefix up to r minus the prefix up to l β 1. This reduces a range query to two prefix queries.
Difference Array Relation
Explanation: If you maintain the difference array in a BIT, then the point value at x is the prefix sum of differences, enabling range updates and point queries.
Two-Tree Prefix
Explanation: With range updates and range queries, maintain two BITs B1 and B2 so that the effective prefix after updates is x times the first BITβs prefix minus the secondβs prefix.
Complexity Summary
Explanation: Each query/update walks a logarithmic number of indices because each step clears or sets one binary digit. The structure stores one number per index.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Fenwick { 5 int n; 6 vector<long long> bit; // 1-indexed internal array 7 8 Fenwick(int n_ = 0) { init(n_); } 9 10 void init(int n_) { 11 n = max(1, n_); 12 bit.assign(n + 1, 0LL); 13 } 14 15 // O(n) build from 0-based array 'a' 16 Fenwick(const vector<long long>& a) { 17 int m = (int)a.size(); 18 n = max(1, m); 19 bit.assign(n + 1, 0LL); 20 for (int i = 1; i <= m; ++i) bit[i] = a[i - 1]; 21 // Propagate each node to its parent to form partial sums 22 for (int i = 1; i <= n; ++i) { 23 int j = i + (i & -i); 24 if (j <= n) bit[j] += bit[i]; 25 } 26 } 27 28 // Add 'delta' to index 'idx' (1-indexed) 29 void add(int idx, long long delta) { 30 for (; idx <= n; idx += idx & -idx) bit[idx] += delta; 31 } 32 33 // Prefix sum S(idx) = sum_{i=1..idx} a[i] 34 long long sumPrefix(int idx) const { 35 long long res = 0; 36 for (; idx > 0; idx -= idx & -idx) res += bit[idx]; 37 return res; 38 } 39 40 // Range sum [l, r] inclusive 41 long long rangeSum(int l, int r) const { 42 if (l > r) return 0; 43 l = max(l, 1); 44 r = min(r, n); 45 return sumPrefix(r) - sumPrefix(l - 1); 46 } 47 }; 48 49 int main() { 50 // Example usage 51 vector<long long> a = {5, 1, 4, 2, 3}; // indices 1..5 52 Fenwick ft(a); // O(n) build 53 54 cout << "Initial prefix sums:\n"; 55 for (int i = 1; i <= (int)a.size(); ++i) { 56 cout << "S(" << i << ") = " << ft.sumPrefix(i) << '\n'; 57 } 58 59 cout << "\nRange sum [2, 4] = " << ft.rangeSum(2, 4) << '\n'; // 1+4+2 = 7 60 61 // Point update: a[3] += 6 (indexing is 1-based in the tree) 62 ft.add(3, 6); 63 cout << "After adding 6 at position 3, range [2, 4] = " << ft.rangeSum(2, 4) << '\n'; // 1+(4+6)+2 = 13 64 65 return 0; 66 } 67
This program defines a 1-indexed Fenwick Tree with an O(n) linear build. It demonstrates prefix sums, range sums (as prefix(r) β prefix(l β 1)), and a single point update that adjusts the partial sums in O(log n).
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Fenwick { 5 int n; vector<long long> bit; 6 Fenwick(int n_ = 0) { init(n_); } 7 void init(int n_) { n = max(1, n_); bit.assign(n + 1, 0LL); } 8 void add(int idx, long long delta) { for (; idx <= n; idx += idx & -idx) bit[idx] += delta; } 9 long long sumPrefix(int idx) const { long long s = 0; for (; idx > 0; idx -= idx & -idx) s += bit[idx]; return s; } 10 }; 11 12 // Supports: add v to range [l, r], and query value at a single point x 13 struct FenwickRangeUpdatePointQuery { 14 Fenwick ft; // stores the difference array d 15 FenwickRangeUpdatePointQuery(int n = 0) : ft(n) {} 16 void init(int n) { ft.init(n); } 17 18 // add v to all a[i] for i in [l, r] 19 void addRange(int l, int r, long long v) { 20 if (l > r) return; 21 ft.add(l, v); 22 if (r + 1 <= ft.n) ft.add(r + 1, -v); 23 } 24 25 // value at a single point x 26 long long pointQuery(int x) const { 27 return ft.sumPrefix(x); 28 } 29 }; 30 31 int main() { 32 int n = 8; 33 FenwickRangeUpdatePointQuery ds(n); 34 35 // Start with all zeros. Perform some range increments. 36 ds.addRange(2, 5, 3); // a[2..5] += 3 37 ds.addRange(4, 8, -2); // a[4..8] -= 2 38 ds.addRange(1, 1, 10); // a[1] += 10 39 40 cout << "Point values after range updates:\n"; 41 for (int i = 1; i <= n; ++i) { 42 cout << "a[" << i << "] = " << ds.pointQuery(i) << '\n'; 43 } 44 45 return 0; 46 } 47
We maintain the difference array d in a single Fenwick Tree. A range add [l, r] is performed as d[l] += v and d[r + 1] β= v. The actual value at position x is the prefix sum of d up to x.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Fenwick { 5 int n; vector<long long> bit; 6 Fenwick(int n_ = 0) { init(n_); } 7 void init(int n_) { n = max(1, n_); bit.assign(n + 1, 0LL); } 8 void add(int idx, long long delta) { for (; idx <= n; idx += idx & -idx) bit[idx] += delta; } 9 long long sumPrefix(int idx) const { long long s = 0; for (; idx > 0; idx -= idx & -idx) s += bit[idx]; return s; } 10 }; 11 12 // Maintain array a[1..n] with: add v to [l, r], query sum over [l, r] 13 struct FenwickRangeUpdateRangeQuery { 14 Fenwick B1, B2; // Two BITs implementing the classic trick 15 FenwickRangeUpdateRangeQuery(int n = 0) : B1(n), B2(n) {} 16 void init(int n) { B1.init(n); B2.init(n); } 17 18 // Internal helper to apply add at index idx on both trees 19 void _internalAdd(int idx, long long mul, long long add) { 20 B1.add(idx, mul); 21 B2.add(idx, add); 22 } 23 24 // Add v to all a[i], i in [l, r] 25 void addRange(int l, int r, long long v) { 26 if (l > r) return; 27 _internalAdd(l, v, v * (l - 1)); 28 if (r + 1 <= B1.n) _internalAdd(r + 1, -v, -v * r); 29 } 30 31 // Prefix sum up to x after all range adds 32 long long prefix(int x) const { 33 long long s1 = B1.sumPrefix(x); 34 long long s2 = B2.sumPrefix(x); 35 return x * s1 - s2; 36 } 37 38 // Range sum [l, r] 39 long long rangeSum(int l, int r) const { 40 if (l > r) return 0; 41 return prefix(r) - prefix(l - 1); 42 } 43 }; 44 45 int main() { 46 int n = 10; 47 FenwickRangeUpdateRangeQuery ds(n); 48 49 // Add ranges 50 ds.addRange(2, 7, 5); // a[2..7] += 5 51 ds.addRange(4, 10, 3); // a[4..10] += 3 52 53 cout << "Sum [1, 3] = " << ds.rangeSum(1, 3) << '\n'; // expected: only [2,3] got +5 => 10 54 cout << "Sum [4, 7] = " << ds.rangeSum(4, 7) << '\n'; // expected: (5+3)*4 = 32 55 cout << "Sum [8, 10] = " << ds.rangeSum(8, 10) << '\n'; // expected: 3*3 = 9 56 57 return 0; 58 } 59
We keep two BITs so that the effective prefix after range additions is x * sum(B1, x) β sum(B2, x). This supports both range updates and range sum queries in O(log n).
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Fenwick { 5 int n; vector<long long> bit; 6 Fenwick(int n_ = 0) { init(n_); } 7 void init(int n_) { n = max(1, n_); bit.assign(n + 1, 0LL); } 8 void add(int idx, long long delta) { for (; idx <= n; idx += idx & -idx) bit[idx] += delta; } 9 long long sumPrefix(int idx) const { long long s = 0; for (; idx > 0; idx -= idx & -idx) s += bit[idx]; return s; } 10 11 // Find smallest idx such that sumPrefix(idx) >= k. Requires all frequencies >= 0 and total sum >= k. 12 int kth(long long k) const { 13 int idx = 0; 14 // Largest power of two >= n 15 int bitMask = 1; 16 while (bitMask << 1 <= n) bitMask <<= 1; 17 for (int step = bitMask; step > 0; step >>= 1) { 18 int next = idx + step; 19 if (next <= n && bit[next] < k) { 20 k -= bit[next]; 21 idx = next; 22 } 23 } 24 return idx + 1; // 1-based index 25 } 26 }; 27 28 int main() { 29 // Suppose we maintain frequencies of values 1..10 30 Fenwick freq(10); 31 32 // Insert some values: frequencies at positions 33 vector<int> vals = {1, 2, 2, 3, 5, 5, 5, 9}; 34 for (int v : vals) freq.add(v, 1); 35 36 // Total count is 8. Let's find the median (k = 4) and also k = 6. 37 int k1 = 4, k2 = 6; 38 int idx1 = freq.kth(k1); 39 int idx2 = freq.kth(k2); 40 41 cout << "k=4 -> value " << idx1 << '\n'; // expected 3 (1,2,2,3,...) 42 cout << "k=6 -> value " << idx2 << '\n'; // expected 5 43 44 // Remove one '5' and find k=6 again 45 freq.add(5, -1); 46 cout << "After removing a 5, k=6 -> value " << freq.kth(6) << '\n'; 47 48 return 0; 49 } 50
The BIT stores frequencies. Binary lifting on the treeβs implicit blocks finds the smallest index with cumulative frequency β₯ k in O(log n). This is useful for dynamic order-statistics such as finding medians or selecting the k-th element.