I learnt of a nice, formal way to prove the correctness of Fenwick trees in terms of orbits that I wish to reproduce here.
One can use a Fenwick tree to perform cumulative sums , and updates . Naively, cumulative sums can take time and updates take time.
A Fenwick tree can perform both in . In general, we can perform any monoid-based catenation and update in , as long as our queries start from the 0th index till some index . So we can only handle monoidal fold queries of the form and point updates.
§ organization
We allow indexes . The node with factorization , (that is, is the highest power of in ) is responsible for the interval .
I'm going to state all the manipulations in terms of prime factorizations, since I find it far more intuitive than bit-fiddling. In general, I want to find a new framework to discover and analyze bit-fiddling heavy algorithms.
Some examples of the range of responsibility of an index are:
- (Subtract )
- (Subtract )

§ query
To perform a cumulative sum, we need to read from the correct overlap regions that cover the full array. For example, to read from , we would want to read:
- .
So we need to read the indices:
At each location, we strip off the value . We can discover this value with bit-fiddling: We claim that .
Let . We wish to extract the output as , which is the power of 2 that we need to subtract from to strip the rightmost . We get:
That is, we successfully strip off the trailing . Armed with the theory, our implemtation becomes:
#define LSB(x) x&(-x)int a[N];int q(int i) { int s = 0; while (i > 0) { s += a[i]; i -= LSB(i); // strip off trailing 1. } return s;}§ update
To perform an update at , we need to update all locations which on querying overlap with . For example, to update the location , we would want to update:
- .
So we need to update the indices:
We use the same bit-fiddling technique as above to strip off the value
#define LSB(x) x&(-x)int tree[N];int u(int i, int v) { while (i < N) { tree[i] += v; i += LSB(i); }}§ correctness
We wish to analyze the operations , and . To do this, we are allowed to maintain an auxiliary array which we will manipuate. We will stipulate the conditions of operations on such that they will reflect the values of and , albeit much faster.
We will analyze the algorithm in terms of orbits. We have two operators, one for update called , and one for query called . Given an index , repeatedly applying the query operator gives us the indeces we need to read and accumulate from the underlying array to get the total sum :
Given an index , repeatedly applying the update operator gives us all the indeces we need to add the change to update:
For query and update to work, we need the condition that:
That is, if and only if the query index includes the update location , will the orbits intersect.
The intuition is that we want updates at an index to only affect queries that occur at indeces . Hence, we axiomatise that for an update to be legal, it must the orbits of queries that are at indeces greater than it.
We will show that our operators:
do satisfy the conditions above.
For a quick numerical check, we can use the code blow to ensure that the orbits are indeed disjoint:
# calculate orbits of query and update in fenwick treedef lsb(i): return i & (-i)def U(i): return i + lsb(i)def Q(i): return i - lsb(i)def orbit(f, i): s = set() while i not in s and i > 0 and i < 64: s.add(i); i = f(i) return sif __name__ == "__main__": for q in range(1, 16): for u in range(1, 16): qo = orbit(Q, q); uo = orbit(U, u) c = qo.intersection(uo) print("q:%4s | u:%4s | qo: %20s | uo: %20s | qu: %4s" % (q, u, qo, uo, c)) print("--")§ Case 1:
We note that always decreases the value of , and always increases it. Hence, if , they meet at this point, and . Hence, they meet exactly once as required.
§ Case 2:
As noted above, always decreases and always increases, hence in this case they will never meet as required.
§ Case 3:
Let the entire array have size . Let , where may be empty strings.
Notice that will always strip away rightmost ones in , leading to at some point.
Similarly, will keep on adding new rightmost ones, causing the state to be .
Hence, at some point .