Counting Smaller Prefix Sums
I want to solve one tidy little problem in this post and then squeeze every drop of insight out of it: count how many earlier prefix sums are smaller than the current prefix sum. It looks like a toy, but it is the engine behind a surprising number of real questions, and it is the cleanest motivation I know for reaching past a plain prefix-sum array and picking up a Fenwick tree. By the end I will have a one-pass O(n log n) solution, an animation that walks through it value by value, a catalogue of sibling problems that share the same skeleton, and an honest comparison with the segment tree I deliberately chose not to use.
The Problem, Precisely
Let me fix notation. Given an array a[0..n-1], I build the prefix-sum array
P[0] = 0
P[k] = a[0] + a[1] + ... + a[k-1] for k = 1..n
so there are n + 1 prefix sums, and P[0] = 0 is included on purpose. For each index i I want the count of earlier indices j < i with P[j] < P[i]. Summing that count over every i gives the total number of ordered pairs (j, i) with j < i and P[j] < P[i]. In plain words: the number of ascending pairs inside the prefix-sum array. That single number is what I am after.
Why I Care: Positive-Sum Subarrays
On its own "ascending pairs of prefix sums" sounds abstract, so here is the reduction that makes me care. The sum of the subarray a[j..i-1] is exactly P[i] - P[j]. That subarray sum is strictly positive precisely when P[i] - P[j] > 0, which rearranges to P[j] < P[i]. So every ascending pair of prefix sums corresponds to one subarray with a strictly positive sum, and vice versa:
number of subarrays with sum > 0
= number of pairs (j, i), j < i, with P[j] < P[i]
Counting positive-sum subarrays naively is an O(n^2) double loop. Routing it through prefix sums turns it into the counting problem above, and that is the version a Fenwick tree dispatches in O(n log n). The same template, with the comparison swapped, counts negative-sum subarrays (P[j] > P[i]) or, with two queries, subarrays whose sum lands in a target range.
Brute Force First
I always like to pin down the answer with an obviously-correct baseline before optimizing. Here it is the double loop over prefix-sum pairs:
long long brute(const vector<long long>& P) {
long long ans = 0;
for (int i = 0; i < (int)P.size(); ++i)
for (int j = 0; j < i; ++j)
if (P[j] < P[i]) ++ans;
return ans;
}
Correct, trivial, and quadratic. The cost is the inner scan: for every prefix sum I re-examine all earlier ones. The whole game is to answer "how many earlier values are below this one?" without that linear rescan.
The Fenwick Tree Idea
The inner question is a dynamic rank query: as I sweep the prefix sums left to right, I keep a growing multiset of the values I have already seen, and at each new value I ask how many of them are strictly smaller. A Fenwick tree over the value domain answers exactly that. I treat the BIT as a frequency table indexed by value: position v stores how many times the value v has appeared so far. Then:
- Count smaller:
query(v - 1)returns the number of stored values in[min, v - 1], i.e. strictly less thanv. - Insert:
update(v, +1)records that I have now seen the valuev.
Sweeping once and doing one query plus one update per prefix sum gives O(n log n) overall. The order matters: I query before I insert, so the current value never counts itself, and only genuinely earlier values are in the table.
// per prefix sum, in left-to-right order:
ans += bit.query(rank(P[i]) - 1); // earlier sums strictly smaller
bit.update(rank(P[i]), +1); // now P[i] joins the table
Coordinate Compression
One catch: a Fenwick tree is indexed by small positive integers, but prefix sums can be enormous, negative, or both. I cannot allocate an array indexed by raw sums. The fix is coordinate compression, which I covered in Patterns & Practice and lean on here. I collect all n + 1 prefix sums, sort the distinct values, and replace each prefix sum by its 1-based rank in that sorted list. Ranks live in [1, m] where m is the number of distinct prefix sums, so the BIT needs only m + 1 slots.
Compression also handles equal prefix sums cleanly. Two equal sums share a rank, so query(rank - 1) excludes them, which is what "strictly smaller" demands. If I ever wanted "smaller or equal" instead, I would query rank rather than rank - 1.
A Worked Example
Let me take a = [3, -2, 1, -4, 2]. The prefix sums are
P = [0, 3, 1, 2, -2, 0]
Sorting the distinct values {-2, 0, 1, 2, 3} gives the rank map below, so m = 5.
| prefix sum | −2 | 0 | 1 | 2 | 3 |
|---|---|---|---|---|---|
| rank | 1 | 2 | 3 | 4 | 5 |
Now I sweep P left to right, querying the count strictly below and then inserting:
| step | P[i] | rank | query(rank−1) | running total |
|---|---|---|---|---|
| 0 | 0 | 2 | 0 | 0 |
| 1 | 3 | 5 | 1 | 1 |
| 2 | 1 | 3 | 1 | 2 |
| 3 | 2 | 4 | 2 | 4 |
| 4 | −2 | 1 | 0 | 4 |
| 5 | 0 | 2 | 1 | 5 |
The total is 5. As a sanity check I can enumerate the subarrays of a with a strictly positive sum: [3], [3,-2], [3,-2,1], [1], and [2]. Five of them, matching exactly. The animation below runs this very example. Each step highlights the current prefix sum, shows how many stored sums sit below it, adds that to the tally, and then folds the value into the frequency BIT along the orange update path.
Counting Smaller Prefix Sums
Prefix sums P (swept left to right)
Frequency BIT (indexed by rank 1..m)
The Code
Putting compression and the sweep together, here is the whole solution. It returns the count of smaller prefix-sum pairs, which equals the number of positive-sum subarrays. I have checked it against the brute force on several hundred random arrays with mixed signs, and the two always agree.
#include <bits/stdc++.h>
using namespace std;
struct Fenwick {
int n;
vector<int> bit;
Fenwick(int n) : n(n), bit(n + 1, 0) {}
void update(int i, int delta) { for (; i <= n; i += i & (-i)) bit[i] += delta; }
int query(int i) { int s = 0; for (; i > 0; i -= i & (-i)) s += bit[i]; return s; }
};
long long countSmallerPrefixPairs(const vector<long long>& a) {
int n = a.size();
vector<long long> P(n + 1, 0);
for (int i = 0; i < n; ++i) P[i + 1] = P[i] + a[i];
// coordinate compress the prefix sums to ranks [1, m]
vector<long long> sorted(P);
sort(sorted.begin(), sorted.end());
sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end());
int m = sorted.size();
auto rankOf = [&](long long x) {
return int(lower_bound(sorted.begin(), sorted.end(), x) - sorted.begin()) + 1;
};
Fenwick fw(m);
long long ans = 0;
for (int i = 0; i <= n; ++i) {
int r = rankOf(P[i]);
ans += fw.query(r - 1); // earlier prefix sums strictly smaller
fw.update(r, 1); // record P[i]
}
return ans;
}
The structure is the same one I use for inversion counting: compress, sweep, query-then-update. Only the direction of the comparison changes. That reuse is the whole point of internalizing the pattern.
Strictly Less, At Most, and Range Variants
Small edits to the same loop unlock a family of questions. I keep these straight by remembering that the BIT answers "how many stored values lie in a prefix of the value axis," and everything else is arithmetic on prefix queries.
- Smaller or equal (subarray sum
≥ 0): queryrankinstead ofrank - 1. - Strictly greater (subarray sum
< 0): the answer isseen - query(rank), whereseenis the number of values already inserted. - Sum in a band
[lo, hi]: a subarray sumP[i] - P[j]lands in[lo, hi]whenP[i] - hi ≤ P[j] ≤ P[i] - lo. Two prefix queries (a difference ofquerycalls over the compressed axis) count the qualifying earlier sums. This is the heart of Count of Range Sum.
Other Patterns Where Fenwick Trees Shine
The prefix-sum count is one member of a large family. Every entry below is the same "sweep and ask a prefix-count question" idea wearing a different costume, and I solve all of them with the identical Fenwick skeleton.
- Counting inversions. Pairs
i < jwitha[i] > a[j]. Sweep left to right and ask how many seen values exceed the current one. I walk through this in detail in Patterns & Practice. - Count of smaller numbers after self (LeetCode 315). Sweep right to left, querying the count strictly below each element. The mirror image of the inversion sweep.
- Reverse pairs (LeetCode 493). Pairs
i < jwitha[i] > 2·a[j]. Same sweep, but the threshold is scaled, so I compress botha[i]and2·a[j]. - Count of range sum (LeetCode 327). The banded version above: count subarrays whose sum falls in
[lower, upper]using two prefix queries per step. - Order statistics by rank. A frequency BIT supports "find the k-th smallest inserted value" through binary lifting on the tree in
O(log m), turning the structure into a dynamic ordered multiset. - 2D prefix sums with point updates. A 2D BIT counts points inside axis-aligned rectangles, which generalizes the inversion idea to the plane. Also covered in Patterns & Practice.
Segment Tree: When I Reach for It Instead
It is fair to ask why I did not just use a segment tree, since a count segment tree over the compressed value axis solves this exact problem with the same asymptotics. Point-update +1 at a rank, range-query the count over [1, rank - 1], and the answer falls out in O(n log n) as well. My honest take: for this problem the Fenwick tree is the better tool, and here is the reasoning I apply.
The query I need is a prefix aggregate of a commutative group (a running count) under point updates. That is precisely the niche a Fenwick tree was designed for. It gives me roughly half the memory, a smaller constant factor, and about a dozen lines of code with no recursion, no lazy propagation, and no node arithmetic to get wrong. When the operation is "add at a point, sum over a prefix," I default to the BIT every time.
The segment tree earns its extra weight when the problem grows past a plain prefix count:
- Index-range plus value-threshold together. If the question becomes "among indices
jin[l, r], how many haveP[j] < x," a flat frequency BIT cannot answer it directly, because it has collapsed the index dimension. A merge sort tree (a segment tree whose nodes hold sorted lists) handles it inO(log^2 n), and a wavelet tree does even better. - Subarray order statistics. "The k-th smallest prefix sum in
P[l..r]" or "how many values belowxin a subarray" is the classic persistent segment tree use case. Persistence is natural on a segment tree and awkward on a BIT. - Richer combines or range updates. The moment I need range min, range max, gcd, or lazy range assignment alongside the counting, the segment tree's explicit nodes and lazy tags become essential. A Fenwick tree only cleanly supports invertible, group-like aggregates.
So my rule of thumb is simple. If I can phrase the query as "point update, prefix sum," the Fenwick tree wins on size, speed, and simplicity, and counting smaller prefix sums is squarely in that bucket. The day the query couples an index range with a value threshold, or asks for order statistics inside a subarray, or wants a non-invertible combine, I switch to a segment tree and accept the extra code for the extra power.
Complexity and Takeaways
Building prefix sums is O(n), sorting for compression is O(n log n), and the sweep does n + 1 Fenwick operations at O(log m) each, for O(n log n) overall and O(m) extra space. The lasting lesson I take from this problem is the reduction in both directions: a question about subarray sums becomes a question about ordered pairs of prefix sums, and a question about ordered pairs becomes a sweep with a dynamic rank query. Once a problem is in that shape, the Fenwick tree is almost always the shortest path to O(n log n).