A Fenwick tree (binary indexed tree) stores partial sums over power-of-two blocks: node (0-indexed form below) covers . Point update and prefix query are , with the smallest constant and code of any dynamic range structure. It works for any group operation (sum, XOR, modular product of units). Min/max work only for prefixes with monotone updates.
Implementation (0-indexed, half-open)
template<class T> struct Fenwick {
int n; vector<T> t;
Fenwick(int n = 0) : n(n), t(n) {}
Fenwick(const vector<T>& a) : n(sz(a)), t(a) { // O(n) build
for (int i = 0; i < n; i++) { int j = i | (i + 1); if (j < n) t[j] += t[i]; }
}
void add(int i, T v) { for (; i < n; i |= i + 1) t[i] += v; }
T sum(int r) const { T s{}; for (; r > 0; r &= r - 1) s += t[r - 1]; return s; } // [0, r)
T sum(int l, int r) const { return sum(r) - sum(l); }
int lowerBound(T k) const { // largest p with sum(p) <= k (values >= 0)
int p = 0;
for (int pw = n ? 1 << __lg(n) : 0; pw; pw >>= 1)
if (p + pw <= n && t[p + pw - 1] <= k) p += pw, k -= t[p - 1];
return p; // so the (k+1)-th unit (0-indexed k) is at index p
}
};Descent: with counts as values, lowerBound(k) returns the index of the -th element (0-indexed) of a multiset over in one walk. That’s the fast order-statistics structure when values are known up front.
Proof: the Fenwick tree is correct
Block structure. For index let (the value of the lowest set bit of , a power of two). Node stores the sum over the block — equivalently . This is an exact partition fact about binary numbers: writing in binary as (some prefix), , and clears exactly those trailing zero-then-the-flipped-bit positions of down to .
add(i, v)visits exactly the ancestor chain of . The stepi |= i + 1is algebraicallyi += lowbit(i+1)(OR-ing with turns the trailing run of ones in followed by a into exactly , matching ). Each new has block , and since trivially as , and one checks follows from ‘s trailing-ones run being a strict superset of ‘s (the carry from adding extends it) — so every ancestor’s block contains , and the walk strictly increases and terminates at (the loop bound). Every node whose block contains the updated position gets , and (by the block partition, blocks nest or are disjoint, never partially overlapping — a standard binary-indexed-tree fact) no other node does, sosumqueries after the update reflect exactly one extra in every block covering position .
sum(r)visits a disjoint cover of . The stepr &= r - 1clears the lowest set bit of , i.e. subtracts . Writing in binary as a sum of its set bits’ values (), the walk visits down to , summing at each stop — and ‘s block is exactly where , which is precisely the suffix of not yet covered by later (larger) terms already summed. Each of the bits of contributes exactly one disjoint block, together tiling exactly, so the sum is exact.
build. For each ,t[j] += t[i]where is exactly ‘s immediate successor in the ancestor chainaddwould walk, so this single bottom-up pass pushes each raw value up through that same chain one hop at a time; by the time index is itself processed it already holds the correct cumulative sum over its block, since every index below it in the chain has already pushed into it. Each index contributes to exactly one immediate parent, so the total work is rather than the of separateaddcalls.
lowerBound(k)is binary search by level on a (conceptually) balanced binary structure over blocks of decreasing power-of-two size, descending only into a block when its prefix sum including that block would still be — the standard Fenwick-walk order-statistics argument, correct whenever all values are non-negative (so prefix sums are monotone in the walked position).Tested against brute-force prefix sums on random arrays with random point updates (build, add, sum) and against a monotone-count scan (lowerBound) — all match.
Range operations
| Need | Trick |
|---|---|
| range add, point query | Fenwick over the difference array: add(l, v); add(r, -v), value = sum(i + 1) |
| range add, range sum | two Fenwicks , over and : |
| point update, prefix max with only increasing updates | t[i] = max(t[i], v) walks |
| range max with arbitrary updates | not a Fenwick job: segment tree |
| -th element, rank | counts + descent |
| inversions, “smaller elements to the left” | scan, query, add (after compression) |
template<class T> struct RangeFenwick { // a[l, r) += v, sum over [l, r)
Fenwick<T> d, e; RangeFenwick(int n) : d(n + 1), e(n + 1) {}
void add(int l, int r, T v) { d.add(l, v); d.add(r, -v); e.add(l, v * l); e.add(r, -v * r); }
T prefix(int r) const { return d.sum(r) * r - e.sum(r); }
T sum(int l, int r) const { return prefix(r) - prefix(l); }
};Intermediate values reach about : use __int128 or a modint if they can overflow.
Proof: range-add range-sum via two Fenwicks
Let be the difference array of the range-add updates (so is the value at position after range-add-via-difference-array, same as Prefix Sums and Difference Arrays). The prefix sum is ; swapping summation order, each () is counted once per , i.e. times: . Tracking in Fenwick and in Fenwick (both maintained by the same range-add-as-two-point-updates trick, since ‘s stamped value at a fixed position is just times that position, a constant multiple of the same update) gives
prefix(r) = d.sum(r)*r - e.sum(r)exactly. Tested against direct range-add simulation on random update/query sequences.
2D and higher
struct Fenwick2D { // point add, rectangle sum, 0-indexed half-open
int n, m; vector<vector<ll>> t;
Fenwick2D(int n, int m) : n(n), m(m), t(n, vector<ll>(m)) {}
void add(int x, int y, ll v) { for (int i = x; i < n; i |= i + 1) for (int j = y; j < m; j |= j + 1) t[i][j] += v; }
ll sum(int x, int y) const { ll s = 0; for (int i = x; i > 0; i &= i - 1) for (int j = y; j > 0; j &= j - 1) s += t[i - 1][j - 1]; return s; }
ll sum(int x1, int y1, int x2, int y2) const { return sum(x2, y2) - sum(x1, y2) - sum(x2, y1) + sum(x1, y1); }
};- 2D range add + range sum: four 2D Fenwicks storing , , , of the 2D difference array; then with the right shifts. .
| Structure | Build | Query | Update | Memory |
|---|---|---|---|---|
| 2D Fenwick | ||||
| 2D segment tree (rows of column-segment-trees) | ||||
| 2D segment tree, sparse (nodes allocated on demand) | — |
No lazy propagation in 2D
Range updates on a 2D segment tree with lazy propagation don’t work in general — a lazy tag on a row node can’t be pushed into its column tree consistently (there’s no single column-tree node to tag). Use the four-Fenwick range-update trick above (sums only), a sqrt decomposition, or offline processing instead.
Proof:
Fenwick2Dcorrectness, and the four-Fenwick 2D range formula
Fenwick2Dapplies the 1D add/sum bit-walk independently in each axis:add(x,y,v)loopsi |= i+1over rows while nestingj |= j+1over columns, stamping every (row-block, column-block) pair containing — by the 1D proof above, the set of row-blocks containing and the set of column-blocks containing are each exactly the 1D ancestor chain, so their product is exactly the 2D ancestor set, andsum(x,y)by the symmetric argument sums exactly the 2D blocks tiling . The four-corner inclusion–exclusionsum(x2,y2)-sum(x1,y2)-sum(x2,y1)+sum(x1,y1)is the standard 2D prefix-sum rectangle identity. The four-Fenwick range-add/range-sum formula is the 2D analogue of the 1D two-Fenwick proof above: expanding and swapping summation order, each () is counted times, which expands to — exactly the four terms tracked by , , , respectively.
- dimensions with compile-time sizes: a recursive template
FenwickND<T, N, Ns...>holdingN+1lower-dimensional Fenwicks; per operation and memory. - Everything known offline, no updates: compress both coordinates to and use a dense 2D structure directly — the simplest option when it applies.
- Large coordinates, offline: sort points and queries by , sweep with a 1D Fenwick over compressed (Offline 2D Queries); time, memory — usually the right answer whenever queries can be sorted.
- Large coordinates, online, point updates: a Fenwick of sorted vectors, each with its own small Fenwick (compress per node, offline insertion of the set of per node), per operation with memory. Or a Fenwick of order-statistic trees.
- Online rectangle queries, no updates: a persistent segment tree where version contains all points with ; a rectangle query is the difference of two versions, per query, memory.
- Online, with insertions: a k-d tree, per rectangle query, memory.
On trees
- Subtree sum with point updates: Euler tour turns subtrees into ranges .
- Path sum from the root with point updates: add at and at ; the root-to- sum is a prefix sum up to . The – path is .
- Path add, point query: add at both endpoints and subtract at the LCA and its parent; the value of is the subtree sum of .
Proof: the tree-on-Fenwick formulas
Subtree-as-range: ‘s subtree is exactly the set of vertices visited between ‘s entry and exit in a DFS Euler tour (every descendant is entered after and exited before ‘s own exit, and nothing else is), so a Fenwick over tour positions answers subtree-sum as a range-sum query. Root-to- via stamping : stamping at and at makes the Fenwick’s prefix sum at position equal , and holds exactly when is in ‘s subtree, i.e. is an ancestor of (or itself) — so the prefix sum at is exactly of values over all ancestors of including , the root-to- sum. The - path formula and its proof are identical to Prefix Sums and Difference Arrays’s tree path-sum proof, with there equal to here. Path add, point query is the Euler-tour transplant of the same note’s tree-difference-array proof: stamping with and lca, parent(lca) with makes the subtree-sum (now a Fenwick range-sum over ) at equal exactly for on the path and otherwise, by the identical ancestor-counting argument.
Notes
- Use the 1-indexed variant (
i += i & -i) if you prefer; the 0-indexedi |= i + 1/r &= r - 1form avoids off-by-one shifts with half-open ranges. - Fenwick over values replaces
std::setfor rank queries and is 5-10x faster. - XOR Fenwick:
+=becomes^=, and range XOR issum(r) ^ sum(l). - Persistence: querying old versions of a Fenwick is awkward (each
addtouches scattered nodes, so naive path-copying doesn’t save anything) — reach for a persistent segment tree instead whenever historical queries are needed.