Fenwick Tree (Binary Indexed Tree) — Comprehensive Guide
A Fenwick tree — also called a Binary Indexed Tree (BIT) — gives prefix aggregates with point updates in O(log N) using one flat array and a single bit trick. It does less than a segment tree but with far less code and a smaller constant factor. When your aggregate is invertible (sum, xor) and you need point-update + prefix/range-query, the BIT is the right tool.
Quick Navigation: “I need to…”
| I need to… | Technique | Section |
|---|---|---|
| Prefix sum + point update | Basic BIT | 2 |
Understand the i & -i magic |
The lowbit trick | 3 |
| Range update + point query | Difference BIT | 5 |
| Range update + range query | Two-BIT trick | 6 |
| Find k-th element / order statistic | BIT descent | 7 |
| Prefix xor with updates | BIT with xor | 4 |
| 2D grid point-update + submatrix sum | 2D BIT | 8 |
| Count inversions / smaller-after-self | Value-indexed BIT | 9 |
| See a full LeetCode problem worked (307/315/493) | Walkthroughs | 9.5 |
| Decide BIT vs segment tree | Comparison | 10 |
Table of Contents
- Why a Fenwick Tree
- Basic Fenwick Tree
- The lowbit Trick
- The Invertibility Requirement
- Range Update, Point Query
- Range Update, Range Query
- Binary Search on a BIT
- 2D Fenwick Tree
- Common Patterns Collection
- Worked LeetCode Problems
- Fenwick vs Segment Tree
- Pattern Recognition Cheat Sheet
1. Why a Fenwick Tree
You need prefix sums and updates, interleaved:
Concretely: you have an array and must process 10^5 interleaved update(i, x) and sumRange(l, r) operations. A prefix-sum array answers each query in O(1) but every update forces an O(N) rebuild; a plain array updates in O(1) but each sum is O(N) — both are O(N·Q) overall and blow up. The BIT makes both operations O(log N), so the whole workload is O(Q log N).
| Approach | Point update | Prefix query |
|---|---|---|
| Plain array | O(1) | O(N) |
| Prefix-sum array | O(N) (rebuild) | O(1) |
| Fenwick tree | O(log N) | O(log N) |
| Segment tree | O(log N) | O(log N) |
Fenwick and segment tree have the same asymptotic cost. The Fenwick wins on code size (≈10 lines) and constant factor (one array, no recursion, cache-friendly). It loses on generality: it can only do invertible aggregates (Section 4).
The cleanest way to hold the relationship in your head: a Fenwick tree is the segment tree you get
to skip building when the operation is invertible. A segment tree needs explicit nodes because it
must answer [l, r] directly. If the operation has an inverse, you never need [l, r] directly —
you get it as prefix(r) − prefix(l−1). That single concession removes the need to store children,
navigate down from a root, or handle a general range at all. What is left is the i ± lowbit(i)
index arithmetic below, which is the tree, walked implicitly.
The Idea
Index i (1-based) is responsible for a range of length lowbit(i) ending at i, where lowbit(i) = i & (-i) is the lowest set bit. A prefix sum walks down by stripping lowest bits; an update walks up by adding them.
Responsibility ranges (1-indexed, n=8):
idx: 1 2 3 4 5 6 7 8
bit: 1 10 11 100 101 110 111 1000
len: 1 2 1 4 1 2 1 8
tree[1] covers a[1]
tree[2] covers a[1..2]
tree[3] covers a[3]
tree[4] covers a[1..4]
tree[6] covers a[5..6]
tree[8] covers a[1..8]
Where the lowbit rule comes from
That table looks arbitrary until you see it is forced, not chosen. Start from the requirement and the lowbit rule falls out.
The requirement. Every prefix a[1..k] must be expressible as a sum of a few stored blocks.
Write k in binary and peel its set bits from the top: k = 11 = 1011₂ = 8 + 2 + 1, so
a[1..11] = a[1..8] + a[9..10] + a[11]
^^^^^^^ ^^^^^^^^ ^^^^^
8 = 1000 2 = 10 1 = 1 block lengths are exactly k's set bits
Each block ends at a running total of the bits consumed so far — 8, then 8+2 = 10, then
8+2+1 = 11. So if we store each block at the index where it ends, the block ending at index i
has length equal to i’s lowest set bit. That is the rule: tree[i] covers
a[i - lowbit(i) + 1 .. i]. It is not a clever trick someone invented; it is the unique layout
that makes binary decomposition of a prefix work.
Both walks are now determined. Reading prefix(k) means consuming k’s set bits from the
bottom, and k -= lowbit(k) does exactly that — 11 → 10 → 8 → 0, summing
tree[11] + tree[10] + tree[8], which is the three blocks above.
Updating a[i] must touch every block containing i. Those are found by i += lowbit(i): from
i = 5 you get 5 → 6 → 8, and indeed tree[5] covers a[5], tree[6] covers a[5..6], and
tree[8] covers a[1..8] — the three blocks containing index 5, and no others.
The two walks move in opposite directions because they answer opposite questions: which blocks tile this prefix (strip bits) versus which blocks contain this index (add bits). Both consume at most one bit position per step, so both are O(log n).
Master LeetCode Comparison Table
The BIT shows up on LeetCode whenever you need updates interleaved with prefix/range queries, or the classic “count elements with property X seen so far” sweep. The worked problems in §9.5 map to these:
| LC # | Problem | Difficulty | BIT is indexed by… | What prefix() answers |
Template |
|---|---|---|---|---|---|
| 307 | Range Sum Query - Mutable | Medium | array position | running sum of a[0..i] |
Basic BIT (§2) |
| 315 | Count of Smaller Numbers After Self | Hard | value rank | how many smaller values seen so far | Value-indexed BIT (§9) |
| 493 | Reverse Pairs | Hard | value rank | how many inserted values ≤ x | Value-indexed BIT (§9) |
| 327 | Count of Range Sum | Hard | prefix-sum rank | how many prefix sums in a window | Value-indexed BIT (§9) |
| 1649 | Create Sorted Array through Instructions | Hard | value rank | count strictly-less / strictly-greater | Value-indexed BIT (§9) |
The split is stark: 307 indexes by position (the array is the data); everything else indexes by value (the BIT counts occurrences along a value axis you compress first). Spotting which axis you’re on is the whole battle.
2. Basic Fenwick Tree
Point update, prefix sum, range sum. 1-indexed internally; the API below takes 0-indexed positions.
Reading the examples: the diagrams and walks in this guide show the internal 1-indexed
tree[]; the code’s public methods take 0-indexed positions and doi += 1at the boundary. So a 0-indexed positionpin the API maps to internal indexp + 1.
class BIT:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1) # 1-indexed, index 0 unused
def update(self, i, delta): # a[i] += delta (0-indexed i)
i += 1
while i <= self.n:
self.tree[i] += delta
i += i & (-i) # move to next responsible index (add lowbit)
def prefix(self, i): # sum of a[0..i] (0-indexed i)
i += 1
s = 0
while i > 0:
s += self.tree[i]
i -= i & (-i) # strip lowbit, jump to previous range
return s
def range_sum(self, l, r): # sum of a[l..r] inclusive
return self.prefix(r) - (self.prefix(l - 1) if l > 0 else 0)
Build in O(N)
Naive build is N updates = O(N log N). Linear build: add each value to its parent.
Note — this is a method on the
BITclass above, not standalone code. The indentation is deliberate: paste it inside the class body.
@classmethod
def from_array(cls, data):
bit = cls(len(data))
for i, v in enumerate(data):
bit.tree[i + 1] += v
for i in range(1, bit.n + 1):
j = i + (i & (-i))
if j <= bit.n:
bit.tree[j] += bit.tree[i]
return bit
Complexity
| Op | Time |
|---|---|
| Build (linear) | O(N) |
| Point update | O(log N) |
| Prefix / range query | O(log N) |
| Space | O(N) |
Worked Trace on Real Values
Take a = [3, 2, -1, 5] (0-indexed). After building, the internal 1-indexed tree[] holds each index’s responsibility range:
internal idx: 1 2 3 4
covers: a[0] a[0..1] a[2] a[0..3]
tree[]: 3 5 -1 9
tree[1] = a[0] = 3
tree[2] = a[0] + a[1] = 3 + 2 = 5
tree[3] = a[2] = -1
tree[4] = a[0]+a[1]+a[2]+a[3] = 3+2-1+5 = 9
Now prefix(2) (0-indexed → sum of a[0..2] = 3 + 2 + (-1) = 4). The code does i += 1, so it descends from internal index 3:
i = 3 (011): add tree[3] = -1 strip lowbit(1) -> i = 2
i = 2 (010): add tree[2] = 5 strip lowbit(2) -> i = 0 stop
sum = tree[3] + tree[2] = -1 + 5 = 4 ✓ (matches a[0]+a[1]+a[2])
3. The lowbit Trick
lowbit(i) = i & (-i) isolates the lowest set bit. Two’s complement makes -i flip-all-bits-plus-one, so i & -i leaves only that lowest 1.
i = 12 = 0000 1100
-i = 1111 0100 (two's complement)
i & -i = 0000 0100 = 4 (the lowbit)
The Two Walks
UPDATE (climb): i += lowbit(i)
a[6] changes (0-indexed) -> internal index 7 (a[6] maps to i = 6 + 1 = 7)
7 -> 8 -> (past n, stop)
QUERY (descend): i -= lowbit(i)
prefix up to index 7:
7 (0111) -> strip -> 6 (0110) -> strip -> 4 (0100) -> strip -> 0 stop
tree[7] + tree[6] + tree[4] = a[7] + a[5..6] + a[1..4] = a[1..7] ✓
Update adds lowbit (goes to the wider range containing you); query subtracts lowbit (jumps to the previous disjoint chunk). The two are exact inverses of each other — that symmetry is why 10 lines suffice.
4. The Invertibility Requirement
Range query is prefix(r) - prefix(l-1). That subtraction is mandatory: a BIT can only answer a range if the operation has an inverse.
| Aggregate | Inverse exists? | BIT works? |
|---|---|---|
| Sum | yes (subtract) | ✓ |
| XOR | yes (xor again) | ✓ |
| Count | yes | ✓ |
| Product (no zeros) | yes (divide) | ✓ (careful with 0) |
| Min / Max | no | ✗ — use segment tree |
| GCD | no | ✗ — use segment tree |
Prefix XOR Variant
Same code, swap + for ^ and identity stays 0 (xor is its own inverse).
class XorBIT:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
def update(self, i, val): # a[i] ^= val
i += 1
while i <= self.n:
self.tree[i] ^= val
i += i & (-i)
def prefix_xor(self, i):
i += 1; x = 0
while i > 0:
x ^= self.tree[i]
i -= i & (-i)
return x
def range_xor(self, l, r):
return self.prefix_xor(r) ^ (self.prefix_xor(l - 1) if l > 0 else 0)
★ Insight ─────────────────────────────────────
This is the single most important “when to use” line: min/max/gcd have no inverse, so a plain BIT cannot do range-min/max — reach for a segment tree instead. (There is a max-BIT that supports prefix-max with point-increase-only updates, but it can’t do arbitrary decreases or true range-max. Don’t force it.)
─────────────────────────────────────────────────
5. Range Update, Point Query
Flip the roles using a difference array stored in a BIT. To add x to a[l..r]: diff[l] += x, diff[r+1] -= x. Then a[i] = prefix_sum(diff, i).
class RangeUpdateBIT:
def __init__(self, n):
self.bit = BIT(n)
self.n = n
def range_add(self, l, r, x): # a[l..r] += x
self.bit.update(l, x)
if r + 1 < self.n:
self.bit.update(r + 1, -x)
def point_query(self, i): # value at a[i]
return self.bit.prefix(i) # prefix of the difference array = current value
Query is now the point value, update is the range. Mirror image of the basic BIT.
6. Range Update, Range Query
The two-BIT trick. Support both range-add and range-sum in O(log N). Derived from expanding the difference-array prefix sum algebraically.
Let d be the difference array (d[l]+=x, d[r+1]-=x). Then:
sum(a[1..i]) = sum_{k=1..i} ( (i+1) * d[k] - k * d[k] )
= (i+1) * prefix(B1, i) - prefix(B2, i)
where B1 tracks d[k], B2 tracks k*d[k].
class RangeRangeBIT:
def __init__(self, n):
self.n = n
self.b1 = BIT(n) # tracks d[k]
self.b2 = BIT(n) # tracks k * d[k]
def range_add(self, l, r, x): # a[l..r] += x (0-indexed inclusive)
self.b1.update(l, x)
if r + 1 < self.n: self.b1.update(r + 1, -x)
self.b2.update(l, x * l)
if r + 1 < self.n: self.b2.update(r + 1, -x * (r + 1))
def prefix(self, i): # sum a[0..i]
return (i + 1) * self.b1.prefix(i) - self.b2.prefix(i)
def range_sum(self, l, r):
return self.prefix(r) - (self.prefix(l - 1) if l > 0 else 0)
This does what a lazy segment tree does (range add + range sum) in ~half the code — but only for invertible aggregates. For range-assign or range-min, you still need the segment tree.
7. Binary Search on a BIT
Find the smallest index whose prefix sum reaches k in a single O(log N) descent — no O(log²N) query-in-a-binary-search. Walk bits from high to low.
def find_kth(bit, k):
"""Smallest 0-indexed i with prefix(i) >= k. Assumes non-negative values."""
pos = 0
log = bit.n.bit_length()
for b in range(log, -1, -1):
nxt = pos + (1 << b)
if nxt <= bit.n and bit.tree[nxt] < k:
pos = nxt
k -= bit.tree[nxt]
return pos # 0-indexed answer is pos (1-indexed pos+1)
Classic use: value-indexed BIT of counts → find_kth returns the k-th smallest value present. Same job as the segment-tree descent, fewer lines.
8. 2D Fenwick Tree
Point update + prefix-rectangle sum on a grid, in O(log M · log N). Nest the climb/descend in both dimensions.
class BIT2D:
def __init__(self, rows, cols):
self.R, self.C = rows, cols
self.tree = [[0] * (cols + 1) for _ in range(rows + 1)]
def update(self, r, c, delta): # grid[r][c] += delta (0-indexed)
i = r + 1
while i <= self.R:
j = c + 1
while j <= self.C:
self.tree[i][j] += delta
j += j & (-j)
i += i & (-i)
def prefix(self, r, c): # sum of grid[0..r][0..c]
s = 0
i = r + 1
while i > 0:
j = c + 1
while j > 0:
s += self.tree[i][j]
j -= j & (-j)
i -= i & (-i)
return s
def rect_sum(self, r1, c1, r2, c2): # inclusive rectangle
return (self.prefix(r2, c2) - self.prefix(r1 - 1, c2)
- self.prefix(r2, c1 - 1) + self.prefix(r1 - 1, c1 - 1))
Inclusion-exclusion on four prefix rectangles gives the submatrix sum — same shape as the 2D prefix-sum formula, but now updatable.
9. Common Patterns Collection
Count Inversions
Sweep right-to-left over value-ranks; each element asks “how many already-seen values are smaller.”
This guide is canonical for the counting / order-statistics family (LC 315, LC 493, CSES Inversions). Segment Tree §7 shows the same sweep on a segment tree — worth reading only if you already have one built for another part of the problem.
def count_inversions(arr):
order = sorted(set(arr))
rank = {v: i for i, v in enumerate(order)}
bit = BIT(len(order))
inv = 0
for x in reversed(arr):
r = rank[x]
inv += bit.prefix(r - 1) if r > 0 else 0 # smaller values seen so far
bit.update(r, 1)
return inv
Count of Smaller Numbers After Self
Same sweep, collect per-element instead of summing.
Dynamic Range Sum (CSES)
Basic BIT (Section 2) — read point updates and range queries, dispatch.
Range Add + Range Sum (CSES “Range Update Queries”)
Two-BIT trick (Section 6).
K-th Remaining Element
Value/position BIT of 1s + find_kth descent (Section 7); set leaf to 0 on removal.
9.5 Worked LeetCode Problems
Three problems, worked end to end: one position-indexed, two value-indexed. Every solution below was executed against the LeetCode sample cases before publishing.
Problem 307 — Range Sum Query - Mutable
Difficulty: Medium
Given an integer array
nums, support two operations, interleaved and many times:update(index, val)setsnums[index] = val, andsumRange(left, right)returns the sum ofnums[left..right]inclusive.
This is the BIT’s reason to exist stated as a LeetCode problem: a prefix-sum array would answer sumRange in O(1) but every update costs O(N) to rebuild; a plain array flips the costs. The BIT makes both O(log N).
The only wrinkle over the §2 template: update on LeetCode is an assignment (nums[index] = val), but a BIT stores deltas. So convert: the delta to apply is val - nums[index], and you must keep a plain copy of nums to know the old value.
class NumArray:
def __init__(self, nums):
self.nums = nums[:] # keep current values to compute deltas
self.bit = BIT(len(nums))
for i, v in enumerate(nums):
self.bit.update(i, v)
def update(self, index, val):
delta = val - self.nums[index] # assignment → delta
self.nums[index] = val
self.bit.update(index, delta)
def sumRange(self, left, right):
return self.bit.range_sum(left, right)
Trace on nums = [1, 3, 5]:
sumRange(0, 2) = prefix(2) - prefix(-1) = 9 - 0 = 9 ✓
update(1, 2): delta = 2 - 3 = -1 → bit.update(1, -1) (nums now [1,2,5])
sumRange(0, 2) = 8 ✓
★ Insight ─────────────────────────────────────
- The assignment-vs-delta gap is the single most common 307 bug: calling
bit.update(index, val)instead ofbit.update(index, val - old)silently corrupts every later sum. A BIT never stores absolute values — always feed it the change. - The shadow
self.numscopy is O(N) extra space but unavoidable with a delta structure: the BIT alone cannot tell you the current value at a point in O(1) (that would be arange_sum(i, i), still O(log N)), and you need the old value before overwriting it.─────────────────────────────────────────────────
Problem 315 — Count of Smaller Numbers After Self
Difficulty: Hard
Given an integer array
nums, return a new arraycountswherecounts[i]is the number of elements to the right ofnums[i]that are smaller thannums[i].
Nothing about this says “prefix sum” — but “how many smaller elements to the right” is exactly a value-axis count if you sweep right to left. Process elements from the end; before inserting each value, ask the BIT “how many already-inserted values are strictly smaller than me?” Everything already inserted is, by construction, to the right.
The BIT is indexed by value rank, not array position. Compress values to ranks 0..m-1 first so the value axis is dense.
def countSmaller(nums):
order = sorted(set(nums))
rank = {v: i for i, v in enumerate(order)}
bit = BIT(len(order))
res = [0] * len(nums)
for i in range(len(nums) - 1, -1, -1): # right to left
r = rank[nums[i]]
res[i] = bit.prefix(r - 1) if r > 0 else 0 # count of strictly-smaller seen
bit.update(r, 1) # register this value
return res
Trace on nums = [5, 2, 6, 1] (ranks: 1→0, 2→1, 5→2, 6→3):
i=3 val=1 (r=0): prefix(-1)=0 → res[3]=0; insert rank 0
i=2 val=6 (r=3): prefix(2)=1 → res[2]=1; insert rank 3 (only 1 seen, it's smaller)
i=1 val=2 (r=1): prefix(0)=1 → res[1]=1; insert rank 1 (1 is smaller; 6 is not)
i=0 val=5 (r=2): prefix(1)=2 → res[0]=2; insert rank 2 (1 and 2 are smaller)
res = [2, 1, 1, 0] ✓
★ Insight ─────────────────────────────────────
- The right-to-left sweep is what makes “to the right” free: at the moment you process index
i, the BIT contains exactly the elements to its right. Direction of the sweep encodes the “after self” constraint — no explicit position check needed. prefix(r - 1)counts values with rank strictly less thannums[i]. User - 1, notr, or you’d count equal values as smaller. This ±1 on the query bound is where “smaller” vs “smaller-or-equal” problems diverge.- Coordinate compression (
sorted(set(...))) keeps the BIT size at O(distinct values) instead of O(max value) — essential when values are large or negative (as-1here).─────────────────────────────────────────────────
Problem 493 — Reverse Pairs
Difficulty: Hard
Count pairs
(i, j)withi < jandnums[i] > 2 · nums[j].
Same value-indexed BIT, but with a twist that trips people up: the comparison value (2·nums[j]) is different from the value you insert (nums[j]). Sweep left to right; for each j, the BIT holds all earlier elements, and you ask “how many of them exceed 2·nums[j]?”
Because 2·nums[j] may not be one of the array’s values, you can’t look it up by rank directly — use bisect to find where it would sit in the compressed order.
from bisect import bisect_left, bisect_right
def reversePairs(nums):
order = sorted(set(nums))
bit = BIT(len(order))
count = 0
inserted = 0
for j in range(len(nums)):
x = 2 * nums[j]
idx = bisect_right(order, x) # ranks [0..idx-1] hold values ≤ x
le_count = bit.prefix(idx - 1) if idx > 0 else 0
count += inserted - le_count # the rest are strictly greater
rj = bisect_left(order, nums[j])
bit.update(rj, 1) # insert nums[j] at its own rank
inserted += 1
return count
★ Insight ─────────────────────────────────────
- The query value and the insert value live on the same compressed axis but need not be the same number.
bisect_right(order, 2·nums[j])maps an arbitrary threshold onto that axis without requiring it to be a real element — the reusable trick for any “count elements past a scaled/shifted threshold” problem. - “Strictly greater than x” is computed as
inserted - (count ≤ x). Complementing against the running insert count is cleaner than a suffix query and avoids a second prefix call — the same complement trick that turns “at least k” into “total minus fewer than k” everywhere.─────────────────────────────────────────────────
10. Fenwick vs Segment Tree
The core decision this guide exists to answer.
| Fenwick (BIT) | Segment Tree | |
|---|---|---|
| Aggregates | Invertible only: sum, xor, count | Any associative: sum, min, max, gcd, … |
| Range min / max | ✗ | ✓ |
| Range update + range query | ✓ (two-BIT trick, invertible only) | ✓ (lazy, any op) |
| Range assign (set to x) | ✗ | ✓ (lazy) |
| Code size | ~10 lines | ~40+ lines |
| Constant factor | Smaller (cache-friendly, no recursion) | Larger |
| Persistence | Awkward | Natural |
| k-th descent | ✓ (Section 7) | ✓ |
| 2D | ✓ (clean) | Possible but heavy |
Decision Flow
Need range min / max / gcd? -> Segment tree (BIT can't invert)
Need range ASSIGN (set all = x)? -> Segment tree (lazy)
Need persistence / historical query? -> Segment tree (persistent)
Just sum/xor + point or range updates? -> BIT (shorter, faster)
2D point update + rectangle sum? -> 2D BIT
Summary: reach for the BIT first when the operation is sum/xor — it’s less code and faster. The moment you hit min/max/gcd, range-assign, or persistence, switch to a segment tree.
11. Pattern Recognition Cheat Sheet
By Problem Signal
| You see… | Reach for | Section |
|---|---|---|
| “Prefix/range sum with updates” | Basic BIT | 2 |
| “Prefix xor with updates” | XOR BIT | 4 |
| “Add x to a range, query single point” | Difference BIT | 5 |
| “Add x to a range, query range sum” | Two-BIT trick | 6 |
| “K-th smallest present / order statistic” | BIT descent | 7 |
| “Count inversions / smaller-after-self” | Value-indexed BIT | 9 |
| “Grid point update + submatrix sum” | 2D BIT | 8 |
| “Range min/max/gcd” | NOT a BIT — segment tree | 10 |
| “Range assign to x” | NOT a BIT — segment tree lazy | 10 |
The Two Lines You Must Memorize
i += i & (-i) # UPDATE: climb to the wider range containing i
i -= i & (-i) # QUERY: descend to the previous disjoint chunk
Common Pitfalls
| Pitfall | Fix |
|---|---|
| Using a BIT for range min/max | No inverse — use segment tree |
| 0-indexed off-by-one | BIT is 1-indexed internally; i += 1 at the boundary |
| Index 0 used | Leave tree[0] unused; loops break at i > 0 |
Range-update but forgot r+1 decrement |
Difference array needs both +x at l and -x at r+1 |
| O(N log N) build in a tight loop | Use the linear from_array build |
| Product BIT with a zero element | Division breaks — guard or use segment tree |
Complexity Summary
| Operation | Time | Space |
|---|---|---|
| Build (linear) | O(N) | O(N) |
| Point update | O(log N) | — |
| Prefix / range query | O(log N) | — |
| Range update (difference / two-BIT) | O(log N) | — |
| k-th descent | O(log N) | — |
| 2D update / query | O(log M · log N) | O(M·N) |
Practice Order
Start here
│
▼
Dynamic Range Sum (CSES, Easy) ──── Basic BIT: point update + range query (§2)
│
▼
Range Update Queries (CSES) ──── Two-BIT trick: range add + range sum (§6)
│
▼
Count Inversions (LC 315 / CSES) ─── Value-indexed BIT: count smaller-so-far (§9)
│
▼
Reverse Pairs (LC 493, Hard) ──── Same BIT, but the query bound is 2*x (§9)
│
▼
K-th / order-statistic descent ──── find_kth: one O(log N) bit walk (§7)
│
▼
2D BIT (Hard) ──── Nest the walk in both dims + inclusion-exclusion (§8)
When This Fails
A Fenwick tree answers query(l, r) as prefix(r) − prefix(l−1), so it lives or dies by
invertibility:
- Non-invertible aggregates. Min, max, and gcd cannot be recovered by subtraction. A “max BIT” exists but only supports prefix maxima with increase-only updates — it cannot do arbitrary range max. Use a segment tree.
- Zero-indexing. The
i & -iwalk requires 1-based indices; index 0 makeslowbitzero and the loop never terminates. Always offset by one internally. - Value-indexed use without compression. Indexing by raw value needs an array the size of the value range. Compress to ranks first.
- No updates at all. A prefix sum is O(1) per query. Do not build a BIT for static data.
- Confusing the two axes. Indexing by position answers “sum of a range of positions”; indexing by value rank answers “how many values below x”. Mixing them up is the most common bug in this family.
Self-Test
Answer these from memory, out loud or on paper, before looking. Recognition is not recall: rereading an explanation feels like knowing, and it is not. A question you cannot answer cold names the exact section to revisit — you do not need to reread the guide.
1. What does i & -i compute, and why is it the whole data structure?
Answer
The lowest set bit of i. It defines each index’s responsibility range: tree[i] covers the i & -i elements ending at i. Every prefix decomposes into O(log n) such ranges — one per set bit of the index — which is exactly why both operations are O(log n).
2. Which direction does update walk, and which direction does query?
Answer
Update climbs to the ancestors that include i: i += i & -i. Query descends through the disjoint blocks composing the prefix: i -= i & -i. Swapping them is a silent wrong answer.
3. Why must a Fenwick tree be 1-indexed?
Answer
0 & -0 == 0, so both loops would fail to advance and spin forever. Store data at internal index i + 1 and translate at the boundary.
4. What can a BIT do that people assume it cannot?
Answer
Range update with range query for sums, using two BITs (one for the linear coefficient, one for the constant). Also order-statistic descent: find the k-th element in one O(log n) bit walk, no binary search wrapper. What it genuinely cannot do is non-invertible aggregates.
5. What is the k-th element descent, and why is it O(log n) rather than O(log² n)?
Answer
Walk down the implicit tree from the highest power of two, greedily taking a step whenever the accumulated count stays below k. It uses the BIT’s own bit structure directly instead of doing a binary search whose every probe costs another O(log n) query.
6. How do you count inversions with a BIT?
Answer
Compress values to ranks, then sweep the array asking, for each element, how many already-inserted values are smaller (or larger, depending on direction) via a prefix query — then insert it. O(n log n) total. LC 315 is this directly; LC 493 is the same sweep with the query bound at 2x.
7. Can you build a BIT in O(n)?
Answer
Yes. Copy the values in, then for each i add tree[i] into its parent i + (i & -i) if that is in range. Each node is pushed to its parent once, giving O(n) instead of n separate O(log n) updates.
8. Position-indexed or value-indexed — which do you need?
Answer
Ask what the index axis means. “Sum of a[l..r]” indexes by position. “How many values less than x have I seen so far” indexes by value rank. Same code, entirely different problems, and picking wrong is the classic bug here.
See Also
- Segment Tree Patterns — when you need min/max/gcd, range assign, lazy propagation, or persistence.
- Prefix Sum Patterns — static (no-update) range queries in O(1).
- Tree Patterns §2 —
tin/toutflattening turns a subtree into a contiguous range, which is what makes subtree-sum-with-point-update a BIT problem. - Binary Search — contrast with §7: the k-th descent walks the BIT’s own bit structure instead of wrapping a query in a search loop.
- Pattern Decision Map — the router: which technique does a cold problem call for?
- Pattern Mastery Program — the spaced-repetition schedule, mastery checklist, and drill formats that turn reading into recall.
Pattern mastered — two lines (i += i & -i to climb, i -= i & -i to descend) plus one question: am I indexing by position or by value?