Introduction
A Fenwick tree is a compact data structure with little code that supports point updates and range queries.
What are “point updates” and “range queries”?
Suppose we have the following problem:
Given a sequence , perform the following two operations:
- Given , increase by .
- Given , find the sum of .
The first operation is a “point update”, and the second is a “range query”.
Similarly, there are “range updates” and “point queries”. Here is one example of each:
- Range update: given , increase every number in by ;
- Point query: given , find the value of .
Notice that range problems are generally strictly stronger than point problems, because an operation on a point is equivalent to an operation on an interval of length .
A standard Fenwick tree maintains information and operations that satisfy associativity and support differences, such as addition (sums), multiplication (products), and XOR.
- Associativity: , where is a binary operator.
- Supporting differences: an operation with an inverse, meaning that given and , we can find .
It is important to note:
- For multiplication modulo a modulus to support differences, every number must have an inverse (which is always true when the modulus is prime);
- Information such as and does not support differences, so it cannot be handled by a standard Fenwick tree. However:
- Two Fenwick trees can be used to handle range minima; see Efficient Range Minimum Queries using Binary Indexed Trees.
- This page also introduces an extended Fenwick tree that supports queries on non-differentiable information in time.
In fact, the problems a Fenwick tree can solve form a subset of those solvable by a segment tree: everything a Fenwick tree can do, a segment tree can certainly do; everything a segment tree can do, a Fenwick tree cannot necessarily do. However, Fenwick-tree code is much shorter than segment-tree code, and its constant factors are smaller, so it is still worth studying.
Sometimes, with the help of a difference array and auxiliary arrays, a Fenwick tree can also solve the stronger problems of range additions with point-value queries and range additions with range-sum queries.
Fenwick tree
First intuition
Let us start with an example: how do we find the prefix sum of ?
One way is to compute , which requires summing numbers.
But what if we know three numbers , , and , where is a sum, is a total, and is a total (which is just itself)? How would you calculate it? You would certainly answer: , requiring only the sum of numbers.
This is why a Fenwick tree can answer queries quickly: we can always split a prefix into no more than intervals, so that the information of these intervals is known.
Thus, we only need to combine the information from these intervals to obtain the answer. Compared with directly combining pieces of information, this is a major improvement in efficiency.
It is easy to see that the information must satisfy associativity; otherwise, it could not be combined as above.
The figure below illustrates how a Fenwick tree works:
The eight blocks at the bottom represent the original data array . The uneven blocks above (which form the same eight-position array as the top row) represent the parent array of , namely .
The array stores the sum of a certain interval of the original array ; in other words, the information for these intervals is known, and our goal is to decompose a queried prefix into these small intervals.
For example, as the figure shows:
- governs ;
- governs ;
- governs ;
- governs ;
- Every remaining governs itself (which can be viewed as the interval of length ).
It is easy to see that always governs an interval whose right endpoint is . We will ignore the left endpoint for now and first build intuition for how a Fenwick tree performs a query.
Example: compute the sum of .
Process: start at and jump backward. We find that governs only the element ; then inspect and find that governs ; then jump to and find that governs the elements ; finally, we try to jump to , but does not actually exist, so we stop.
The values we found are . These are exactly the three small intervals into which was decomposed, so combining them gives .
Example: compute the sum of .
Again, we start at , jump to , and then jump to . We now find that it governs the sum of , but we do not want the part. What should we do? Simply subtract the sum of .
We can instead begin by converting the query for the sum of into a query for the sum of and a query for the sum of , then subtract the two results.
Governed intervals
This raises the question: how far to the left does extend? In other words, what is the interval length?
In a Fenwick tree, the interval governed by is defined to have length , where:
- If the least significant bit is bit , then is exactly the bit position in the binary representation of corresponding to the lowest
1; - (the length of the interval governed by ) is exactly the number formed by the binary representation of 's lowest
1and all subsequent0bits.
For example, which interval does govern?
Because , the lowest 1 and the subsequent 0 bits form the binary number 1000, namely , so governs elements of the array.
Therefore, represents the interval information for .
Let the number formed by the lowest bits of the binary representation of —the 1 bit and all subsequent 0 bits—be . Then the interval governed by is .
Note
is not the bit position of the lowest 1, namely ; it is formed by that 1 and all subsequent 0 bits, namely .
How do we compute lowbit? From bitwise operations, we obtain lowbit(x) = x & -x.
The principle behind lowbit
Taking the complement of every bit in the binary representation of x and then adding 1 gives the binary encoding of -x. For example, the binary encoding of is 110; complementing every bit gives 001, and adding 1 gives 010.
Suppose the original binary encoding of x is (...)10...00. Complementing every bit gives [...]01...11; adding 1 gives [...]10...00, which is the binary encoding of -x. Here, x has first binary bit 1; this is the lowest occurrence in x, namely 1.
Every bit represented by the ellipses in (...) and [...] is complementary, so x & -x = (...)10...00 & [...]10...00 = 10...00, whose result is lowbit.
Implementation
C++
int lowbit(int x) {
// The number formed by the lowest 1 bit of x and all subsequent 0 bits in binary.
// lowbit(0b01011000) == 0b00001000
// ~~~~^~~~
// lowbit(0b01110010) == 0b00000010
// ~~~~~~^~
return x & -x;
}
Python
def lowbit(x):
"""
The value formed by the lowest set bit of x and all trailing zeros in its binary representation.
lowbit(0b01011000) == 0b00001000
~~~~~^~~
lowbit(0b01110010) == 0b00000010
~~~~~~~^~
"""
return x & -x
Range queries
Next, let us examine the concrete operations on a Fenwick tree, starting with range queries.
Recall the process for querying : we converted it into two subqueries, the sum of and the sum of , and then took their difference.
In fact, every range query can be handled this way: the sum of is the sum of minus the sum of , converting a range problem into a prefix problem that is easier to handle.
In contests, converting a range query over into two prefix queries over and , then taking their difference, is a very common technique.
How do we perform a prefix query? Recall the process for querying :
Jump backward from : governs only the element ; then inspect and find that governs ; jump to and find that governs the elements ; then try to jump to . But does not actually exist, so we stop.
The values we found are . These are exactly the three small intervals into which was decomposed; combining them gives .
Observe that at each step we jump to the position immediately to the left of the current interval's left endpoint, making it the right endpoint of the new interval. This is necessary to decompose the prefix without gaps or overlaps. For example, governs , so the next jump is to , accessing .
We can write the process for querying as follows:
- Start from and jump backward; governs .
- Set . If , we have reached the end and terminate the loop; otherwise, return to the first step.
- Combine the values reached.
When implementing this, we do not need to collect all the values first and combine them afterward; we can combine them while jumping.
For example, if the maintained information is a sum, set the initial . Each time we reach a , set ; the final is the result of combining them all.
Implementation
C++
int getsum(int x) { // sum of a[1]..a[x]
int ans = 0;
while (x > 0) {
ans = ans + c[x];
x = x - lowbit(x);
}
return ans;
}
Python
def getsum(x): # sum of a[1]..a[x]
ans = 0
while x > 0:
ans = ans + c[x]
x = x - lowbit(x)
return ans
Fenwick-tree properties and its tree structure
Before discussing point updates, let us first explain several basic properties of a Fenwick tree and the origin of its tree structure; this will make point updates easier to understand.
We use the following conventions:
- . That is, is the left endpoint of the range governed by .
- For every positive integer , we can write in the form , where .
- The statement “ and are disjoint” means that the ranges governed by and do not intersect, namely that and are disjoint. The meaning of statements such as “ is contained in ” is analogous.
Property : for , either and are disjoint, or is contained in .
Proof
Proof: suppose and intersect, meaning that and intersect. Then .
Write as , so . Therefore, can be written as , where .
It is easy to see that . Also, ,
so , that is, .
Therefore, if and intersect, the range governed by must be completely contained in .
Property : is strictly contained in .
Proof
Proof: let and . Then and .
It is easy to see that , so , that is, .
Therefore, is strictly contained in .
Property : for any , and are disjoint.
Proof
Proof: let . Then , where .
It is easy to see that . Also, ,
Therefore, , that is, .
Therefore, and are disjoint.
With these three properties in place, we can now examine the tree structure of a Fenwick tree (ignore the edges from to ).
In fact, the tree structure of a Fenwick tree is the graph formed by connecting to , where is the parent of .
When considering this tree structure, we ignore the size of the Fenwick tree and regard it as an infinite tree for convenience. In an actual implementation, for we only use , where is the length of the original array.
This tree naturally has many useful properties. We list several below (let denote the direct parent of ):
- .
- is greater than every descendant of and less than every ancestor of .
- Node 's is strictly less than 's .
Proof
Let and . Then . It is easy to see that , as required.
- The height of node is , namely the bit position in the binary representation of corresponding to
1.
Definition of height
The height of node , denoted , satisfies: if , then ; otherwise, , where ranges over all children of (in this case, has at least one child, ).
In other words, a node's height is exactly greater than that of its highest child. If a node has no children, its height is .
We introduce height here to make the later complexity analysis easier to explain.
- is strictly contained in (Property ).
- is strictly contained in , where is any ancestor of (by induction on the preceding property).
- strictly contains , where is any descendant of (the preceding property with and exchanged).
- For any , if is not an ancestor of , then and are disjoint.
Proof
Among and the ancestors of , there must be a node such that . By Property , is disjoint from ; since contains , is therefore disjoint from .
- For any , if is not in the subtree of , then and are disjoint (the preceding property with and exchanged).
- For any , the condition that is an ancestor of is equivalent to being strictly contained in (a summary of the preceding properties). This is the core principle behind Fenwick-tree point updates.
- Let . Then the number of its children is , and their indices are .
- For example, suppose and the binary index of is
...1000. Then has three children, whose binary indices are...0111,...0110, and...0100.
- For example, suppose and the binary index of is
Proof
Starting from a number , subtracting flips the bit in the binary representation of at position , while the lower bits remain unchanged.
Consider and one of its children, . We have , so and . Let .
Consider . Bits of at and below position are all , so in , bit becomes while the lower bits remain ; this satisfies .
Consider . Then ; in , bit becomes ; this does not satisfy .
Consider . Then ; in , bit is , so ; this does not satisfy .
- The ranges governed by all children of in concatenate exactly to .
- For example, suppose and the binary index of is
...1000. Then has three children, whose binary indices are...0111,...0110, and...0100. c[...0100]representsa[...0001 ~ ...0100].c[...0110]representsa[...0101 ~ ...0110].c[...0111]representsa[...0111 ~ ...0111].- It is easy to see that the union of the three ranges above is exactly
a[...0001 ~ ...0111], namely .
- For example, suppose and the binary index of is
Proof
Every child of can be written as . It is easy to see that the smaller is, the larger is, so its interval lies farther to the right. Let ; then are the children of from left to right.
It is easy to see that , so .
Consider adjacent children and . The right endpoint of the former's governed interval is , while the left endpoint of the latter's governed interval is ; they meet exactly.
Consider the leftmost child . Its governed left boundary is , exactly .
Consider the rightmost child . Its governed right boundary is .
Therefore, the ranges governed by these children concatenate exactly to .
Point updates
Now let us consider how to perform a point update on .
Our goal is to maintain the array quickly and correctly. For efficiency, we only need to traverse and update the entries that govern , namely the values, because the other values have obviously not changed.
Every entry governing , namely , must contain (by Property ), so is an ancestor of in the Fenwick-tree structure. Therefore, we start at and repeatedly jump to the parent until we pass the length of the original array.
Let denote the size of . The process for a point update on is easy to write:
- Initially set .
- Update .
- Set . If , we have reached the end and terminate the loop; otherwise, return to the second step.
The interval information and the type of point update together determine how to update . Here are several examples:
- If stores a range sum and the update on adds , then add to every corresponding the amount .
- If stores a range product and the update on multiplies by , then multiply every corresponding by .
However, the flexibility of point updates means that the update operation and the maintained information need not use the same operation. For example, if stores a range sum and the update assigns the value , we can convert it into adding to the amount . If instead it multiplies by , we can convert it into adding to the amount .
Here is an implementation that maintains range sums with point additions.
Implementation
C++
void add(int x, int k) {
while (x <= n) { // do not go out of bounds
c[x] = c[x] + k;
x = x + lowbit(x);
}
}
Python
def add(x, k):
while x <= n: # do not go out of bounds
c[x] = c[x] + k
x = x + lowbit(x)
Building the tree
That is, build the Fenwick tree from the original sequence given at the beginning (precomputing all of ).
The straightforward method is to convert this into point updates, with time complexity (the complexity analysis appears below).
For example, given the sequence , to build the tree we can treat it as a point addition of by , a point addition of by , and a point addition of by .
There is also an tree-building method; see Tree building in on this page.
Complexity analysis
The space complexity is clearly .
Time complexity:
- For a range query: the entire iteration process can be viewed as gradually changing, from the lowest bit upward, the binary representation of so that every becomes . The number of intervals decomposed equals the number of bit positions at which the binary representation of has value (that is, ). Therefore, the time complexity of one query is .
- For a point update: when jumping to parents, the height of each visited node strictly increases, and always holds. Since the height of node is , the height reached cannot exceed , so the number of visited values is on the order of . Therefore, the complexity of one point update is .
Range additions with range sums
Prerequisite: prefix sums & differences.
This problem can be solved with two Fenwick trees maintaining a difference array.
Consider sequence 's difference array , where . Since the prefix sum of the difference array is the original array, .
Similarly, we convert a range-sum query into a prefix-sum query using differences. Consider the sum of , namely , and derive it:
Inspecting this expression, it is easy to see that each is added a total of times. Continuing the derivation:
does not determine the value of , so we need two Fenwick trees to maintain the sums of and separately.
How do we perform a range addition? Consider how the original range changes when adding , and how that affects .
Because the difference is ,
- increases by while is unchanged, so increases by .
- is unchanged while increases by , so decreases by .
- For any positions other than and , namely , either and both remain unchanged or both increase by . Thus remains , so all other remain unchanged.
The maintenance method is now clear: in the Fenwick tree maintaining , add at an amount and at an amount ; in the Fenwick tree maintaining , add at an amount and at an amount .
For the weaker problem of “range addition, point-value query”, it is enough to use a Fenwick tree to maintain one difference array . To query the point value at , simply find the sum of .
Here is the code for “range additions with range sums”:
Implementation
C++
int t1[MAXN], t2[MAXN], n;
int lowbit(int x) { return x & (-x); }
void add(int k, int v) {
int v1 = k * v;
while (k <= n) {
t1[k] += v, t2[k] += v1;
// Do not write t2[k] += k * v, because k is no longer an index in the original array
k += lowbit(k);
}
}
int getsum(int *t, int k) {
int ret = 0;
while (k) {
ret += t[k];
k -= lowbit(k);
}
return ret;
}
void add1(int l, int r, int v) {
add(l, v), add(r + 1, -v); // Convert a range addition into two prefix additions
}
long long getsum1(int l, int r) {
return (r + 1ll) * getsum(t1, r) - 1ll * l * getsum(t1, l - 1) -
(getsum(t2, r) - getsum(t2, l - 1));
}
Python
t1 = [0] * MAXN
t2 = [0] * MAXN
n = 0
def lowbit(x):
return x & (-x)
def add(k, v):
v1 = k * v
while k <= n:
t1[k] = t1[k] + v
t2[k] = t2[k] + v1
k = k + lowbit(k)
def getsum(t, k):
ret = 0
while k:
ret = ret + t[k]
k = k - lowbit(k)
return ret
def add1(l, r, v):
add(l, v)
add(r + 1, -v)
def getsum1(l, r):
return (
(r) * getsum(t1, r)
- l * getsum(t1, l - 1)
- (getsum(t2, r) - getsum(t2, l - 1))
)
Following this principle, one can implement “range multiplication with range products”, “XOR a number over a range and query the range XOR”, and so on, as long as the maintained information and the range operation use the same operation. Interested readers can try these themselves.
Two-dimensional Fenwick trees
Point updates, submatrix queries
A two-dimensional Fenwick tree, also called a Fenwick tree of Fenwick trees, maintains point updates and prefix information on a two-dimensional array.
As with a one-dimensional Fenwick tree, let denote the total matrix information for ; that is, the total information of a matrix with as its lower-right corner, height , and width .
For a point update, let:
That is, is the ancestor of at level in the Fenwick-tree structure (the level- ancestor is the node itself).
Then only elements in govern . When updating , we only need to update every with and .
Correctness proof
Suppose governs . Find the possible values of and .
Consider a one-dimensional Fenwick tree of size , (corresponding to the original array ), and a one-dimensional Fenwick tree of size , (corresponding to the original array ).
The proposition is then equivalent to the condition that governs and governs .
In other words, in the Fenwick-tree structure, is one of and its ancestors, and is one of and its ancestors.
Therefore, and .
For a query, let:
Then combine every for which .
Correctness proof
Let denote the operation used to combine two pieces of information (for example, if the information is a range sum, then ).
Consider a one-dimensional Fenwick tree . The expression exactly represents the information for the interval of the original array.
Similarly, let . Then exactly represents the matrix information of .
Likewise, represents the matrix information of .
If we regard the function as a Fenwick tree, it is effectively a Fenwick tree of Fenwick trees; this is the origin of the name “Fenwick tree of Fenwick trees”.
Here is the code for point additions and submatrix-sum queries.
Implementation
Point addition
void add(int x, int y, int v) {
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= m; j += lowbit(j)) {
// A loop variable must be created here; unlike in a one-dimensional array, we cannot simply write while (x <= n)
c[i][j] += v;
}
}
}
Submatrix-sum query
int sum(int x, int y) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
for (int j = y; j > 0; j -= lowbit(j)) {
res += c[i][j];
}
}
return res;
}
int ask(int x1, int y1, int x2, int y2) {
// Query the submatrix sum
return sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1);
}
Submatrix additions, submatrix-sum queries
Prerequisites: prefix sums & differences and the range additions with range sums section on this page.
As with the one-dimensional Fenwick-tree problem of range additions with range sums, consider maintaining a difference array.
The difference array of a two-dimensional array is defined as follows:
Why is it defined this way?
This is because, in the ideal state, taking a two-dimensional prefix sum over the difference matrix should recover the original matrix: the two operations are inverses.
The two-dimensional prefix-sum formula is:
.
Thus, let be the original array and the difference array. We have:
Rearranging gives the formula for two-dimensional differences.
.
Thus, for the submatrix whose upper-left corner is and lower-right corner is , a submatrix addition of is equivalent, on the difference array, to adding at and an amount , and adding at and an amount .
As for why this works, write these four values using their defining formulas and analyze the change in each term.
Consider an example. If the initial difference array is and the submatrix receives an addition of , the difference array becomes:
(The submatrix is exactly the central matrix of size shown above.)
Therefore, a submatrix addition is implemented as four point additions on the difference array.
Now consider a submatrix-sum query:
For a point , its two-dimensional prefix sum can be written as:
The reason is that the prefix sum of the prefix sum of the difference array is the original prefix sum.
As with the one-dimensional range-addition/range-sum problem, the number of occurrences of is .
Continuing the derivation:
Therefore, we need four Fenwick trees, maintaining the sums of , , , and , respectively.
As in the one-dimensional case, if we only need submatrix additions and point-value queries, maintaining one difference array and querying its prefix sum is enough.
Here is the code:
Implementation
using ll = long long;
ll t1[N][N], t2[N][N], t3[N][N], t4[N][N];
void add(ll x, ll y, ll z) {
for (int X = x; X <= n; X += lowbit(X))
for (int Y = y; Y <= m; Y += lowbit(Y)) {
t1[X][Y] += z;
t2[X][Y] += z * x; // Note that this is z * x rather than z * X; the same applies below
t3[X][Y] += z * y;
t4[X][Y] += z * x * y;
}
}
void range_add(ll xa, ll ya, ll xb, ll yb,
ll z) { //(xa, ya) to (xb, yb) submatrix
add(xa, ya, z);
add(xa, yb + 1, -z);
add(xb + 1, ya, -z);
add(xb + 1, yb + 1, z);
}
ll ask(ll x, ll y) {
ll res = 0;
for (int i = x; i; i -= lowbit(i))
for (int j = y; j; j -= lowbit(j))
res += (x + 1) * (y + 1) * t1[i][j] - (y + 1) * t2[i][j] -
(x + 1) * t3[i][j] + t4[i][j];
return res;
}
ll range_ask(ll xa, ll ya, ll xb, ll yb) {
return ask(xb, yb) - ask(xb, ya - 1) - ask(xa - 1, yb) + ask(xa - 1, ya - 1);
}
Weighted Fenwick trees and applications
We know that when a standard Fenwick tree is built directly on the original sequence, represents the range information of .
In fact, we can also build a Fenwick tree over the value-frequency array of the original sequence; this is a weighted Fenwick tree.
What is a value-frequency array?
For a sequence , its value-frequency array satisfies: the value of is the number of occurrences of in .
For example, the value-frequency array of is .
Clearly, the size of is related to the value range of .
If the value range of the original sequence is too large and only relative order matters rather than the exact values, the original array is often discretized before building the value-frequency array.
In addition, a value-frequency array represents the order-insensitive content of the original array: it describes the elements while ignoring their order, so two arrays with the same content in different orders have the same value-frequency array.
Therefore, for problems whose answers do not depend on the order of a given array, reasoning with the value-frequency array is often more intuitive, such as [NOIP2021] Sequence.
Using weighted Fenwick trees, we can solve several classic problems.
Point updates, querying the global -th smallest
Here we discuss only the -th smallest; the -th largest problem can be converted to the -th smallest by a simple calculation.
This problem can be discretized. If the value range of the original sequence is too large, discretize it and then build the value-frequency array . The values involved in point updates must also be discretized; it is not enough to discretize only the elements of the original array .
A point update on the original sequence can be converted directly into a point update on the value-frequency array. Specifically, changing the original value from to becomes a point update on : at , subtract ; at , add .
To query the -th smallest, binary-search and query the prefix sum of in the value-frequency array. Find such that the prefix sum of is while that of is ; then the -th largest number is (note: here the prefix sum of is regarded as ).
This gives a time complexity of .
Consider replacing binary search with binary lifting.
Set and , then enumerate downward from to :
- Query the range sum of in the value-frequency array, denoted .
- If , the extension succeeds: set and ; otherwise, the extension fails and nothing is done.
The resulting is the maximum value for which the prefix sum of is , so the final answer is .
This may appear not to improve the time efficiency, but in fact the range sum query for only needs to access the value of .
The reason is simple. Consider : it must be , because before we only added with . Therefore, the interval represented by is exactly .
Thus, the time complexity decreases to .
Implementation
C++
// Query the k-th smallest in a weighted Fenwick tree
int kth(int k) {
int sum = 0, x = 0;
for (int i = log2(n); ~i; --i) {
x += 1 << i; // Try to extend
if (x > n || sum + t[x] >= k) // Extension failed
x -= 1 << i;
else
sum += t[x];
}
return x + 1; // Return n + 1 if it cannot be found
}
Python
# Query the k-th smallest in a weighted Fenwick tree
def kth(k):
sum = 0
x = 0
i = int(log2(n))
while ~i:
x = x + (1 << i) # Try to extend
if x > n or sum + t[x] >= k: # Extension failed
x = x - (1 << i)
else:
sum = sum + t[x]
i = i - 1
return x + 1 # Return n + 1 if it cannot be found
Global inversions (global two-dimensional partial order)
Related reading and reference implementation: inversions
Global inversions can also be solved elegantly with a weighted Fenwick tree. The problem is: given , the length of a sequence , count the pairs in satisfying and , written as .
This problem can be discretized. If the value range of the original sequence is too large, discretize it and then build the value-frequency array .
We enumerate in reverse from down to using index , treating it as the index of the first element of an inversion. We then count how many satisfy , and accumulate the answer.
In fact, we only need to do the following (let the current ):
- Query the prefix sum of , which is the number of inversions whose left endpoint is .
- Increase by ;
The reason is natural: every element appearing in is smaller than the current . Because is enumerated in reverse, these elements already in the value-frequency array naturally have original-array indices greater than the index currently being visited.
As an example, let .
We scan in the order :
- : query the prefix sum of , obtaining ; increase by , giving .
- : query the prefix sum of , obtaining ; increase by , giving .
- : query the prefix sum of , obtaining ; increase by , giving .
- : query the prefix sum of , obtaining ; increase by , giving .
- : query the prefix sum of , obtaining ; increase by , giving .
Therefore, the final answer is .
Notice that after enumerating , the two steps—querying and increasing —can be reversed: increase first, then query , without changing the answer. There are two ways to see this:
- Updating does not affect the query on .
- After reversal, we are effectively counting pairs with and . When , is impossible, so is equivalent to ; this is therefore equivalent to the original inversion problem.
If we query the number of non-strict inversions ( and ), we must instead query the sum of , and the two steps can no longer be reversed. Again, there are two ways to see this:
- A modification to affects queries on .
- After reversing the process, what is actually being queried is the number of pairs satisfying and . Since always holds when , is not equivalent to , so this is not equivalent to the original problem.
If we want to query the number of pairs satisfying and , these two steps need to be reversed.
Another way to solve the original inversion-pair problem is to enumerate in increasing order and query how many satisfy . The procedure is as follows (let ):
- Query the interval sum of ( is the size of , namely the value range of (or its discretized value range)).
- Increment by .
The reason is that every element appearing in is greater than the current . Since is enumerated in increasing order, these elements already stored in the value array naturally have original-array indices smaller than the currently visited index .
In addition, inversion pairs can also be counted using merge sort. This method avoids discretization. Its time complexity is likewise . Reference implementations of both algorithms can be found in the inversion pairs section.
Fenwick tree maintenance of non-invertible information
For example, maintaining interval extrema.
Although this method uses little code, note that both point updates and interval queries have time complexity , which is worse than the time complexity of a segment tree.
Interval queries
We still follow the previous idea: starting from , repeatedly jump forward along , but we must not jump to the left of .
Therefore, if we have jumped to , first determine whether the next position, , would be less than :
- If it is less than , we directly merge the single point into the aggregate information, and then jump to .
- If it is greater than or equal to , no boundary has been crossed, so merge normally and then jump to .
Here is the code, using an interval maximum query as an example:
Implementation
int getmax(int l, int r) {
int ans = 0;
while (r >= l) {
ans = max(ans, a[r]);
--r;
for (; r - lowbit(r) >= l; r -= lowbit(r)) {
// Note that the loop condition must not be written as r - lowbit(r) + 1 >= l
// Otherwise, when l = 1, r will jump to 0 and enter an infinite loop
ans = max(ans, C[r]);
}
}
return ans;
}
It can be proved that the time complexity of the above algorithm is .
Time complexity proof
Consider the highest bit at which and differ. At this bit, must contain and must contain (because ).
If still has a after this bit, then must hold, so the next step must fill the lowest bit of with ;
If the at this bit of is itself the lowest bit of , then whether we perform or , the at this bit of will become .
Therefore, after at most transformations of , the highest bit at which and differ must decrease by one. Thus, the total time complexity is .
Point updates
Note
Please first understand the following two properties of the Fenwick tree structure before studying this section.
- Let . Then it has children, whose indices are respectively .
- The intervals governed by corresponding to all children of concatenate exactly into .
The meaning and proof of these two properties can be found in the section Fenwick tree and properties of its tree structure on this page.
After updating , we only need to update those for which is an ancestor of in the Fenwick tree structure.
For extrema (taking the maximum as an example), a common incorrect idea is that if is changed to , then every should be updated to . Here is a counterexample: change to in . The maximum is , but the update above would produce . Directly changing to is also incorrect; a counterexample is to change to in the example above.
In fact, for non-invertible information, there is no way to directly modify using . This is because a modification is equivalent to removing the old number from the original interval and then adding a new number. The effect of removing it on the interval information is equivalent to performing an inverse operation, but non-invertible information has no inverse operation, so cannot be modified directly.
In other words, for every affected , we must reconstruct the information for that interval.
Consider the children of . Their information is necessarily correct (because we update the children before the parent), and these children together form the governed interval . Merging one more single point, , therefore produces , which is precisely . In this way, we can reconstruct and merge at most intervals to obtain each that needs to be updated.
Implementation
void update(int x, int v) {
a[x] = v;
for (int i = x; i <= n; i += lowbit(i)) {
// Enumerate the affected intervals
C[i] = a[i];
for (int j = 1; j < lowbit(i); j *= 2) {
C[i] = max(C[i], C[i - j]);
}
}
}
It is easy to see that the time complexity of the above algorithm is .
Building the tree
We can split this into single-point updates, giving a construction complexity of .
There is also an construction method; see method one in the tree construction section of this page.
Tricks
tree construction
Take maintaining interval sums as an example.
Method one:
The value of each node is obtained by summing the values of all its directly connected children. Therefore, we can consider the contributions in reverse: after determining a child's value, use it to update its direct parent.
Implementation
C++
// Θ(n) tree construction
void init() {
for (int i = 1; i <= n; ++i) {
t[i] += a[i];
int j = i + lowbit(i);
if (j <= n) t[j] += t[i];
}
}
Python
# Θ(n) tree construction
def init():
for i in range(1, n + 1):
t[i] = t[i] + a[i]
j = i + lowbit(i)
if j <= n:
t[j] = t[j] + t[i]
Method two:
As mentioned earlier, the interval represented by is . We can therefore precompute a prefix-sum array and then calculate the array.
Implementation
C++
// Θ(n) tree construction
void init() {
for (int i = 1; i <= n; ++i) {
t[i] = sum[i] - sum[i - lowbit(i)];
}
}
Python
# Θ(n) tree construction
def init():
for i in range(1, n + 1):
t[i] = sum[i] - sum[i - lowbit(i)]
Timestamp optimization
This is a common technique for handling multiple data sets. If we clear the Fenwick tree by brute force every time new data is input, a time limit may be exceeded. We therefore use a marker to store the last time each node was used (that is, which data set most recently used it). During each operation, compare the time in this position's with the current time to determine whether the position should contain or its value in the array.
Implementation
C++
// Timestamp optimization
int tag[MAXN], t[MAXN], Tag;
void reset() { ++Tag; }
void add(int k, int v) {
while (k <= n) {
if (tag[k] != Tag) t[k] = 0;
t[k] += v, tag[k] = Tag;
k += lowbit(k);
}
}
int getsum(int k) {
int ret = 0;
while (k) {
if (tag[k] == Tag) ret += t[k];
k -= lowbit(k);
}
return ret;
}
Python
# Timestamp optimization
tag = [0] * MAXN
t = [0] * MAXN
Tag = 0
def reset():
Tag = Tag + 1
def add(k, v):
while k <= n:
if tag[k] != Tag:
t[k] = 0
t[k] = t[k] + v
tag[k] = Tag
k = k + lowbit(k)
def getsum(k):
ret = 0
while k:
if tag[k] == Tag:
ret = ret + t[k]
k = k - lowbit(k)
return ret
Example problems
- Fenwick tree 1: point updates, interval queries
- Fenwick tree 2: interval updates, point queries
- Fenwick tree 3: interval updates, interval queries
- Two-dimensional Fenwick tree 1: point updates, interval queries
- Two-dimensional Fenwick tree 2: interval updates, point queries
- Two-dimensional Fenwick tree 3: interval updates, interval queries