Exercises — Fenwick tree (Binary Indexed Tree) — prefix sums, O(log n) update and query
Throughout, arrays are 1-indexed (Fenwick trees must be — see the parent's mistake box). We reuse the running array
A quick reminder of the two moves you'll use constantly:
Level 1 — Recognition
These check that you can read a cell, a lowbit, and a block without running any algorithm.
L1.1 Write for .
L1.2 Which elements does store the sum of? What is that sum for our array?
L1.3 True or false: index is contained in the blocks of cells . Justify with lowbit arithmetic.
Recall Solution L1.1
is the largest power of two dividing — the value of the rightmost bit.
| binary | lowbit | |
|---|---|---|
| 1 | 0001 | 1 |
| 2 | 0010 | 2 |
| 3 | 0011 | 1 |
| 4 | 0100 | 4 |
| 5 | 0101 | 1 |
| 6 | 0110 | 2 |
| 7 | 0111 | 1 |
| 8 | 1000 | 8 |
Recall Solution L1.2
, so covers the block ending at of length : indices .
Recall Solution L1.3
The chain of cells containing index is up to (the update path). Start (), → next . At (), → next . At (), → next stop. So the cells are . True.
Level 2 — Application
Run the actual loops by hand.
L2.1 Compute by listing the query path and the cells added.
L2.2 Starting from our array, perform update(j=5, δ=+4). List every cell touched.
L2.3 After the update in L2.2, compute the range sum .
Recall Solution L2.1
. Query moves down by stripping the lowest bit: Cells added: .
- covers : .
- covers : .
- covers : . Direct check: . ✓
Recall Solution L2.2
(). Update moves up by adding lowbit: Cells touched: — each gets . These are exactly the cells whose blocks contain index : , , .
Recall Solution L2.3
. Before the update ; the landed on , which is inside prefix , so new . : , cells .
- , . So . (Index , unaffected by the update.) Direct check: . ✓
Level 3 — Analysis
Reason about paths, edge cases, and structure.
L3.1 For , which single index has the longest update path, and how long is it? Which has the longest query path?
L3.2 Prove that the query path length equals the number of -bits in (its popcount).
L3.3 Degenerate input: what does the update loop do if you call update(j=0, δ)? Why is this a silent bug, and how does 1-indexing dodge it?
Recall Solution L3.1
Update path (moves up, ): the worst case starts small and climbs. Starting at : stop — that's cells touched. Every doubling adds one step, so the length is for . Any odd index near (e.g. ) hits this bound. Query path (moves down, strips one -bit each step): longest for the index with the most set bits . For that's (four -bits) → path , 4 cells. (Index has only one bit → path length .)
Recall Solution L3.2
Each query step does . Subtracting the lowest set bit from clears exactly that one bit (it turns the rightmost into and touches nothing else, since is a pure power of two aligned to that bit). The loop stops when , i.e. when all -bits are gone. Therefore the number of iterations = number of -bits initially present = . Since , the query is .
Recall Solution L3.3
. The update step never changes . So the loop condition j <= n stays true forever → infinite loop (or, if you guard it, the update silently does nothing). It's silent because no exception fires; the code just spins or no-ops.
1-indexing dodges it: real element indices become , never . Every valid index has a lowest set bit, so both loops always progress. (See parent lowbit insight.)
Level 4 — Synthesis
Combine Fenwick with other ideas from the parent's connections.
L4.1 (Inversion counting.) Count inversions in the permutation — pairs with but — using a Fenwick tree over value slots. Give the count and the running trace. See Inversion counting.
L4.2 (Range update / point query via difference array.) We want to support range add add(l,r,δ) and point read read(k). Using a difference-style Fenwick (see Range update with difference array): describe the two updates and one query, then apply add(2,4,+5) on a zero array of size and read index and index .
L4.3 (Order statistics.) A Fenwick tree stores counts of present values in . Currently values are present (so count at is , at is , at is , at is ). Find the th smallest present value using the binary-lifting on the tree method. See Order statistics.
Recall Solution L4.1
Idea: scan left to right; before inserting , ask "how many already-inserted values are greater than ?" Those form inversions with . A Fenwick over value slots gives how many inserted are via a prefix query; greater-than count (inserted so far) prefix.
Start with empty tree over slots . Let = number inserted so far.
| step | insert | before | so far | inversions added |
|---|---|---|---|---|
| 1 | 3 | 0 | 0 | |
| 2 | 1 | 1 | 0 | |
| 3 | 4 | 2 | 2 | |
| 4 | 2 | 3 | 1 |
(At step 4, values inserted = just the , so ; the and are both greater → inversions.) Check by hand: the pairs are — exactly . ✓
Recall Solution L4.2
Difference trick: keep a Fenwick over a difference array where the point read is a prefix sum of . To add on : do update(l, +δ) and update(r+1, -δ). Then read(k) = query(k) (prefix sum) gives the current value at , because the turns on at and the turns it off after .
Apply add(2,4,+5) on size- zero array: update(2,+5), update(5,-5) (since ).
read(3) = query(3)= sum of = (the at slot is included, the at slot is not). → . Correct: index .read(5) = query(5)= . Correct: index .
Recall Solution L4.3
Binary lifting walks the tree from the top power-of-two down, greedily jumping right while the accumulated count stays . We want the smallest index whose prefix count .
Counts by slot: slot2=1, slot3=2, slot6=1, slot8=1 (others ). The Fenwick cells (block sums) are what we actually read, but conceptually we track a running total acc and current position pos=0.
Highest power of two is . Try jumps :
- jump : cell sum . ? Yes → don't take it (we'd overshoot). Stay.
- jump : cell sum slot2+slot3 . → take.
pos=4, acc=3. - jump : cell sum . . Is ? No → don't take.
- jump : cell sum . → take.
pos=5, acc=3. Answer index . Check: sorted present multiset is ; the th smallest is . ✓
Level 5 — Mastery
Deeper reasoning; each answer forces you to reconstruct the invariants.
L5.1 You are given a black-box Fenwick array tree[1..8] with values
Recover the original array without re-running the build. (Hint: single-element recovery.)
L5.2 Prove the linear build is correct: after for i in 1..n: if i+lowbit(i)<=n: tree[i+lowbit(i)] += tree[i] (starting from tree[i]=a[i]), every cell holds its correct block sum.
L5.3 Design & justify: how do you get a single element from a completed Fenwick tree in using only query? Then do it for using L5.1's tree.
Recall Solution L5.1
Single element: , but we can recover directly. For a cell whose block has length , subtract the sub-blocks it swallowed. The clean rule: where ranges over the children down to . Easiest here is . Let's just run queries:
- → .
- → .
- → .
- → .
- → .
- → .
- → .
- → . (Our original array — the tree was built from it.) ✓
Recall Solution L5.2
Claim: when the loop reaches index , tree[i] already equals its correct block sum .
Why: cell 's block is composed of (its own initial value) plus the blocks of all immediate children — cells with and . Each such child satisfies , so it was processed earlier in the increasing loop and, by induction, was already complete when it "donated" tree[c] into tree[c+lowbit(c)] = tree[i]. By the time we read to donate upward, every child has finished contributing, so tree[i] is complete. The base case (leaf cells, ) hold immediately with no children. Hence one increasing pass completes all cells.
(Cost: each does one add → .)
Recall Solution L5.3
Method: . Each query is , so the difference is . (A slicker walk that subtracts overlapping cells exists, but two queries is simplest and same asymptotics.) For : from L5.1, and , so ✓
Recall One-line self-test
Query direction? ::: subtract lowbit, move toward . Update direction? ::: add lowbit, move toward . from a built tree? ::: .
Connections
- Parent: Fenwick tree (Binary Indexed Tree) — prefix sums, O(log n) update and query
- Prefix sum array · Segment tree · Two's complement representation · Binary representation of integers
- Inversion counting · Order statistics · Range update with difference array