Fenwick Tree - Range Update Range Query
Key Points
- •A Fenwick Tree (Binary Indexed Tree) can support range additions and range sum queries by maintaining two trees, often called B1 and B2.
- •Think in terms of a difference array d: adding v to [l, r] means increases by v and decreases by v.
- •Store d in B1 and store (i−1)· in B2; then prefix sum up to x is x·sum(B1, x) − sum(B2, x).
- •Range sum over [l, r] equals prefix(r) − prefix(l−1) using the combined query from both BITs.
- •Each update or query runs in O(log n) time, and the structure uses O(n) space.
- •Use 1-indexing internally for Fenwick operations; convert from 0-indexed inputs carefully.
- •Long long (64-bit) is recommended to avoid overflow when multiplying indices and values.
- •This approach is simpler and faster than a full segment tree with lazy propagation for pure range-add/range-sum tasks.
Prerequisites
- →Prefix Sums — Fenwick Trees generalize prefix sums with efficient updates; understanding prefix-to-range conversion is essential.
- →Difference Arrays — Range additions become two point updates on the difference array, the foundation of the two-BIT trick.
- →Bit Manipulation (lowbit) — Fenwick traversal uses the least significant set bit (x & −x) to navigate.
- →1-indexed vs 0-indexed Arrays — Fenwick implementations are typically 1-indexed; careful conversions prevent off-by-one errors.
- →Integer Overflow and 64-bit Arithmetic — Indices multiply values in queries; using long long prevents overflow on large inputs.
- →Segment Tree Basics (optional contrast) — Helps understand when two-BIT is preferable or when a segment tree with lazy propagation is needed.
Detailed Explanation
Tap terms for definitions01Overview
A Fenwick Tree (also called a Binary Indexed Tree, or BIT) is a compact data structure for maintaining prefix sums with logarithmic-time updates and queries. By default, a single Fenwick Tree supports point updates and prefix sum queries. However, many problems require applying the same increment to an entire range and then asking for the sum of elements over a range. With a neat trick, we can extend the Fenwick Tree to support Range Update and Range Query (RURQ) in O(log n) per operation by maintaining two Fenwick Trees simultaneously. The core idea is to view the array through its difference array d, where each range increment translates into only two point updates in d. Then, because we want sums of the original array, we track both d and a weighted version of d so that we can reconstruct any prefix sum using a simple formula. This method is elegant, memory-efficient, and very fast in practice, making it popular in competitive programming and systems that need high-throughput cumulative operations.
02Intuition & Analogies
Imagine a long row of cups labeled 1 through n. The value a_i is how much water sits in cup i. If we pour v units of water into every cup from l to r, doing it cup-by-cup is slow. Instead, consider tracking changes at the boundaries: place a sign at l that says "+v starts here" and another at r+1 that says "−v stops here". When you walk from cup 1 to cup x, you tally the sum of all signs you've passed; that tells you how much has been added to each cup up to x. This 'signs' method is exactly the difference array d. Now, if someone asks for the total water in cups 1..x, it's not enough to know how much each individual cup gained; you need the combined total. Here's a second insight: if the +v effect started far back, it contributes to many cups; if it started recently, it affects fewer. That means older starts are 'heavier' for the prefix sum. We capture this by keeping another tally that weights each boundary sign by where it appears. With two ledgers—one for raw starts/stops and one for their weighted positions—you can compute the total water efficiently for any prefix. A Fenwick Tree is the perfect tool because it supports quick updates to these ledgers and quick queries of their cumulative totals.
03Formal Definition
04When to Use
Use a two-BIT (Fenwick) approach when you need to support two operations efficiently: adding a constant to every element of a subarray [l, r], and querying the sum of elements over a subarray [l, r]. This shows up in problems involving repeated range increments, difference constraints, frequency range updates, prefix-sum-based scoring, and cumulative counters where updates are bulk but queries are diverse. It is especially effective in competitive programming where O(log n) per operation at scale (n, q up to 2e5 or more) is required. If you only need point updates and range sums, a single Fenwick is sufficient. If you need more complex operations (like range minimum, max, or affine updates), consider a segment tree with lazy propagation. However, for pure range add and range sum, the two-BIT solution is simpler, often faster due to low constants, easier to implement correctly under time pressure, and uses O(n) space. It’s also a good fit when memory is constrained but performance demands are high.
⚠️Common Mistakes
- Off-by-one indexing: Fenwick trees are naturally 1-indexed. If your inputs are 0-indexed, you must convert l and r to 1-based before updates and queries. 2) Forgetting the r+1 update: Range add [l, r] requires B1.add(l, v) and B1.add(r+1, −v) (similarly for B2). Skipping the r+1 update breaks the 'stop' boundary and yields incorrect tails. 3) Miscomputing B2 updates: For range add v to [l, r], you must add v·(l−1) at l and −v·r at r+1. Using v·l or v·(r+1) is wrong and will shift sums. 4) Overflow: Since prefix computations multiply by indices (up to n), use 64-bit integers (long long) for both trees and intermediate products, especially if values or n can be large. 5) Querying the wrong function: The prefix value of a_x is D(x), but the prefix sum P(x) requires the combined formula x·B1.query(x) − B2.query(x). Don’t return D(x) when asked for range sums. 6) Not guarding r+1 bounds: Only apply updates at r+1 if r < n. Allocate arrays of size at least n+2 to simplify edge handling. 7) Mixing inclusive/exclusive ranges: Fenwick-based range updates assume inclusive [l, r]. Be consistent across problem I/O and internal methods. 8) Building from an initial array incorrectly: To build from a, convert to differences d and update B1 with d_i and B2 with (i−1)·d_i; or simulate rangeAdd(i, i, a_i).
Key Formulas
Difference Array Definition
Explanation: This defines the difference array d for a. It encodes how values change between consecutive indices, making range additions become two point updates.
Recover a from d
Explanation: The original array value at position x equals the sum of differences up to x. Walking across differences accumulates all started increments.
Prefix Sum via Differences
Explanation: Here D(x) = and ID(x) = i . This identity reconstructs the sum of the first x elements using d and its index-weighted sum.
Range Sum from Prefix Sums
Explanation: Any inclusive range sum can be obtained as the difference of two prefix sums. This is the standard conversion from prefix to range queries.
Range Add as Two Point Updates
Explanation: Adding v to every in [l, r] only changes the boundaries in d: it starts at l and stops at r+1. If , the r+1 update is omitted.
Two-BIT Prefix Formula
Explanation: If B1 stores D(x) and B2 stores (i-1) , then the prefix sum of a up to x is x·B1.query(x) − B2.query(x). This matches the implementation-friendly formula.
Lowbit Operation
Explanation: The least significant set bit determines Fenwick jumps. Adding it moves upward to parent; subtracting it moves downward to accumulate sums.
Time and Space Complexity
Explanation: Each add or query touches at most one node per bit in the index, giving logarithmic time. The tree arrays store O(n) values.
Equivalent Prefix Identities
Explanation: Both forms are mathematically identical. Storing (i−1)· as the second BIT makes the computational form x·D(x) − (ID(x) − D(x)) convenient.
Range Sum via Two-BIT Prefix
Explanation: Apply the prefix formula at r and l−1 and subtract. This is exactly what rangeSum(l, r) does in code.
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 Fenwick tree 7 Fenwick() : n(0) {} 8 explicit Fenwick(int n_) { init(n_); } 9 void init(int n_) { 10 n = n_; 11 bit.assign(n + 1, 0LL); 12 } 13 // Add value v at index i (1-indexed) 14 void add(int i, long long v) { 15 for (; i <= n; i += i & -i) bit[i] += v; 16 } 17 // Prefix sum query sum_{1..i} 18 long long sum(int i) const { 19 long long res = 0; 20 for (; i > 0; i -= i & -i) res += bit[i]; 21 return res; 22 } 23 }; 24 25 struct FenwickRange { 26 int n; 27 Fenwick B1, B2; // B1 stores d_i, B2 stores (i-1)*d_i 28 FenwickRange() : n(0) {} 29 explicit FenwickRange(int n_) { init(n_); } 30 void init(int n_) { 31 n = n_; 32 B1.init(n); 33 B2.init(n); 34 } 35 // Internal helper: add v to difference at position i on B1 and (i-1)*v on B2 36 void _diff_add(int i, long long v) { 37 if (i <= 0) return; // guard if someone passes 0 38 if (i > n) return; // ignore r+1 if it is n+1 and trees sized n 39 B1.add(i, v); 40 B2.add(i, v * (i - 1)); 41 } 42 // Range add: add v to a[l..r], with 1 <= l <= r <= n 43 void range_add(int l, int r, long long v) { 44 _diff_add(l, v); 45 if (r + 1 <= n) _diff_add(r + 1, -v); 46 } 47 // Prefix sum of a up to x: P(x) = x*sum(B1,x) - sum(B2,x) 48 long long prefix_sum(int x) const { 49 if (x <= 0) return 0LL; 50 if (x > n) x = n; 51 long long s1 = B1.sum(x); 52 long long s2 = B2.sum(x); 53 return s1 * x - s2; 54 } 55 // Range sum over [l, r] 56 long long range_sum(int l, int r) const { 57 if (l > r) return 0LL; 58 l = max(l, 1); 59 r = min(r, n); 60 return prefix_sum(r) - prefix_sum(l - 1); 61 } 62 }; 63 64 int main() { 65 ios::sync_with_stdio(false); 66 cin.tie(nullptr); 67 68 int n = 10; 69 FenwickRange fw(n); 70 71 // Add 5 to [3, 7] 72 fw.range_add(3, 7, 5); 73 // Add -2 to [6, 10] 74 fw.range_add(6, 10, -2); 75 76 // Query some ranges 77 cout << fw.range_sum(1, 10) << "\n"; // total sum 78 cout << fw.range_sum(3, 3) << "\n"; // a[3] 79 cout << fw.range_sum(7, 9) << "\n"; // sum over [7..9] 80 81 return 0; 82 } 83
This code builds a two-BIT structure to support range additions and range sum queries. B1 holds d_i and B2 holds (i−1)·d_i. Range updates are translated into boundary updates on both trees, and queries use P(x) = x·sum(B1, x) − sum(B2, x).
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Fenwick { 5 int n; vector<long long> bit; 6 Fenwick() : n(0) {} 7 explicit Fenwick(int n_) { init(n_); } 8 void init(int n_) { n = n_; bit.assign(n + 1, 0); } 9 void add(int i, long long v){ for(; i <= n; i += i & -i) bit[i] += v; } 10 long long sum(int i) const { long long r=0; for(; i>0; i -= i & -i) r += bit[i]; return r; } 11 }; 12 13 struct FenwickRange { 14 int n; Fenwick B1,B2; 15 FenwickRange(): n(0) {} 16 explicit FenwickRange(int n_) { init(n_); } 17 void init(int n_) { n=n_; B1.init(n); B2.init(n); } 18 void _diff_add(int i, long long v){ if(i<=0||i>n) return; B1.add(i,v); B2.add(i, v*(i-1)); } 19 void range_add(int l,int r,long long v){ _diff_add(l,v); if(r+1<=n) _diff_add(r+1,-v); } 20 long long prefix_sum(int x) const { if(x<=0) return 0; if(x>n) x=n; long long s1=B1.sum(x), s2=B2.sum(x); return s1*x - s2; } 21 long long range_sum(int l,int r) const { if(l>r) return 0; l=max(l,1); r=min(r,n); return prefix_sum(r)-prefix_sum(l-1); } 22 // Build from initial array a[1..n] 23 void build_from_array(const vector<long long>& a){ 24 // Build differences and load into B1, B2 25 for(int i=1;i<=n;i++){ 26 long long di = a[i] - (i>1 ? a[i-1] : 0LL); 27 _diff_add(i, di); 28 } 29 } 30 }; 31 32 int main(){ 33 ios::sync_with_stdio(false); 34 cin.tie(nullptr); 35 36 int n = 8; 37 vector<long long> a(n+1); // 1-indexed 38 // initial array 39 a[1]=3; a[2]=1; a[3]=4; a[4]=1; a[5]=5; a[6]=9; a[7]=2; a[8]=6; 40 41 FenwickRange fw(n); 42 fw.build_from_array(a); 43 44 // Perform some range updates 45 fw.range_add(2, 5, 10); 46 fw.range_add(4, 8, -3); 47 48 // Naive checker 49 vector<long long> naive = a; 50 for(int i=2;i<=5;i++) naive[i]+=10; 51 for(int i=4;i<=8;i++) naive[i]+=-3; 52 53 // Compare multiple queries 54 for(int l=1;l<=n;l++){ 55 for(int r=l;r<=n;r++){ 56 long long fast = fw.range_sum(l,r); 57 long long slow = 0; for(int i=l;i<=r;i++) slow += naive[i]; 58 if(fast != slow){ 59 cerr << "Mismatch at ["<<l<<","<<r<<"] fast="<<fast<<" slow="<<slow<<"\n"; 60 return 0; 61 } 62 } 63 } 64 cout << "All checks passed!\n"; 65 return 0; 66 } 67
Shows how to initialize the two BITs from an existing array by computing differences d_i and inserting them. It also validates correctness against a naive array updated element-wise.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Fenwick{ 5 int n; vector<long long> bit; 6 void init(int n_){ n=n_; bit.assign(n+1,0); } 7 void add(int i,long long v){ for(; i<=n; i+=i&-i) bit[i]+=v; } 8 long long sum(int i) const { long long r=0; for(; i>0; i-=i&-i) r+=bit[i]; return r; } 9 }; 10 11 struct FenwickRange{ 12 int n; Fenwick B1,B2; 13 void init(int n_){ n=n_; B1.init(n); B2.init(n); } 14 void _diff_add(int i,long long v){ if(i<=0||i>n) return; B1.add(i,v); B2.add(i, v*(i-1)); } 15 void range_add(int l,int r,long long v){ _diff_add(l,v); if(r+1<=n) _diff_add(r+1,-v); } 16 long long prefix_sum(int x) const { if(x<=0) return 0; if(x>n) x=n; long long s1=B1.sum(x), s2=B2.sum(x); return s1*x - s2; } 17 long long range_sum(int l,int r) const { if(l>r) return 0; l=max(l,1); r=min(r,n); return prefix_sum(r)-prefix_sum(l-1); } 18 }; 19 20 int main(){ 21 ios::sync_with_stdio(false); 22 cin.tie(nullptr); 23 24 int n, q; cin >> n >> q; 25 FenwickRange fw; fw.init(n); 26 27 // Optional: read initial array and build 28 vector<long long> a(n+1,0); 29 for(int i=1;i<=n;i++){ cin >> a[i]; } 30 // Build by differences 31 for(int i=1;i<=n;i++){ 32 long long di = a[i] - (i>1? a[i-1]:0LL); 33 fw._diff_add(i, di); 34 } 35 36 // Process queries 37 // Type 1 l r v => add v to [l, r] 38 // Type 2 l r => print sum of [l, r] 39 for(int qi=0; qi<q; ++qi){ 40 int type; cin >> type; 41 if(type==1){ 42 int l, r; long long v; cin >> l >> r >> v; 43 fw.range_add(l, r, v); 44 } else if(type==2){ 45 int l, r; cin >> l >> r; 46 cout << fw.range_sum(l, r) << '\n'; 47 } 48 } 49 return 0; 50 } 51
This is a typical CF-style solution skeleton: build from an initial array using differences, then handle two types of operations—range add and range sum—each in O(log n). It uses long long to avoid overflow.