3.4.16 · D4Trees

Exercises — Fenwick tree (Binary Indexed Tree) — prefix sums, O(log n) update and query

2,669 words12 min readBack to topic

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