Prefix sums give range queries on a static array. Difference arrays give range updates that are read at the end. Each is the inverse transform of the other. Check them before reaching for any tree.
| Prefix sum | Difference array | |
|---|---|---|
| Fast operation | range query | range update |
| Slow operation | update () | query () |
| Build | ||
| Finalise | — | prefix pass |
| Best when | all updates precede all queries | all updates precede all queries (the other way round) |
Prefix sums
vector<ll> p(n + 1); // p[i] = a[0] + ... + a[i-1]
for (int i = 0; i < n; i++) p[i + 1] = p[i] + a[i];
// sum of a[l..r) = p[r] - p[l]Half-open indices with remove every special case at the left edge.
2D: , and the rectangle sum is the four-term inclusion-exclusion. In dimensions it takes terms, or one prefix pass per axis (that’s cheaper to build: ).
What can be prefixed: any group operation (sum, XOR, product of non-zero values mod prime). Min, max and gcd aren’t invertible, so use a sparse table (Sparse Table) or prefix and suffix arrays when you only need to exclude one element:
// gcd of the array without a[i]: pre[i] = gcd(a[0..i)), suf[i] = gcd(a[i..n))
ll without(int i) { return gcd(pre[i], suf[i + 1]); }The general version of this trick: for any associative operation with an identity, pre[i] aggregates and suf[i] aggregates , and combine(pre[i], suf[i+1]) excludes position without needing an inverse (unlike subtracting from a prefix sum, which only works when the operation is invertible). Preserve argument order for non-commutative operations, and make sure the sentinel identity values actually match the operation (0 for sum/XOR, 1 for product, a suitably large/small value for min/max). Preprocessing and every exclusion query cost total.
| Trick | Use |
|---|---|
| prefix XOR | subarray XOR in |
| prefix counts per value | ”how many in “ |
| prefix of | “is constant” |
| prefix min/max | not decomposable — use a sparse table |
| prefix product mod | range product, with modular inverses |
| prefix sums of the sorted array | ”sum of the smallest” |
Counting subarrays = counting pairs of prefixes
A subarray is a pair of prefixes, so “count subarrays with property ” becomes “count pairs with ”.
| Subarrays with | Pairs of prefixes | Tool |
|---|---|---|
| sum | hash map of counts | |
| sum divisible by | count residues; remember | |
| XOR | hash map; for “XOR ” or max XOR use a binary trie | |
| equal number of 0s and 1s | map , then sum | hash map, or offset array |
| sum in | BIT over compressed prefixes, or merge sort | |
| longest with sum | first occurrence of each prefix | map value first index |
| max sum | running minimum (Kadane) | |
| max sum with length in | sliding window minimum of | monotonic deque |
| average | subtract , then sum | binary search on |
| median | by comparing with , then sum (or ) | binary search on |
| balanced brackets | , never below and | sparse table on , or stack |
Difference arrays
vector<ll> d(n + 1);
auto add = [&](int l, int r, ll x) { d[l] += x; d[r] -= x; }; // a[l..r) += x
// finalize: prefix-sum d to recover the arrayupdates plus one pass is . Uses: interval coverage counts (“how many intervals contain each point”), booking problems, sweep events, and grid stamping.
2D: add to rows and columns with at , at and , at . Finalize with a 2D prefix sum.
Polynomial updates via higher-order differences. Adding a polynomial of degree over a range needs levels of differences. For an arithmetic progression on positions , with the second difference array (prefix-sum twice to finalize):
D[l] += a;
D[l + 1] += s - a;
D[l + m] -= a + m * s; // a + (m-1)s is the last term
D[l + m + 1] += a + (m - 1) * s;When in doubt, derive boundary terms by writing the first difference array for a tiny example. For degree in general, add -shaped patterns: the -th difference of is a single 1.
Proof: the second-difference boundary terms
Applying the ordinary (first-order) difference-array trick twice means: is the difference array of , which is itself the difference array of the target — so is recovered by prefix-summing twice. The target is for and outside . Its first difference is : (jumping from 0), (the constant step of the AP), and (dropping back to 0 after the last term). is ‘s own difference array (the difference-of-differences): ; ; ; — matching the code exactly.
Tested against direct double prefix-summing, comparing the reconstructed array to the intended AP on random .
Interleaved updates and queries: a Fenwick tree over the difference array gives range add with point query. Two Fenwick trees give range add with range sum, using (Fenwick Tree).
Proof: the two-Fenwick range-sum formula
With the difference array, the value at position is (point query from one Fenwick tree over , already correct for range-add/point-query). The prefix sum is ; swapping the order of summation, each () is counted once for every with , i.e. times: . Tracking and each in their own Fenwick tree (both updated by the same range-add-via-difference-array trick, since at a fixed add position is just a constant multiple of the same update) gives both sums in , and the formula above (with a 0/1-indexing shift matching the code’s ) recovers the range sum.
On trees
Let be the sum of values on the root-to- path. Then (drop the last term for edge values).
Proof: the tree path-sum formula
The root-to- path splits as (root-to-lca) + (lca-to-, exclusive of lca), so where excludes the lca’s own value; symmetrically . The - path is the disjoint union of (lca-to-, excl.) + + (lca-to-, excl.), so . For edge weights (no vertex value to add back), the three pieces are already disjoint without the correction, giving .
Path updates with all reads at the end (the tree difference array): to add on the path –, do d[u] += x; d[v] += x; d[lca] -= x; d[parent(lca)] -= x;. Then each vertex’s value is the sum of d over its subtree, which one DFS computes. For edge values stored at the child endpoint: d[u] += x; d[v] += x; d[lca] -= 2x.
Proof: the tree difference array
Let be the value stamped at (the four updates), and let be the DFS subtree sum the algorithm reports for . The claim is for on the path – and otherwise. Each stamp at a vertex contributes to exactly when is an ancestor of (inclusive). So , where means is an ancestor of or equals . Split on where sits: (1) strictly between lca and (inclusive of , exclusive of lca) — ancestor of only, none of the other three — , matching being on the path. (2) symmetrically for . (3) — ancestor (reflexively) of , , and itself, but not of parent(lca) — , correct. (4) a proper ancestor of lca — ancestor of , , lca, and parent(lca) all at once — , correctly off the path. (5) anywhere else (not an ancestor of or ) — all four indicators are 0 — . This covers every vertex, proving the claim. The edge-value variant stamps only with and lca with (no stamp at parent(lca)): case (3) becomes (correct: the lca has no incident edge above the path’s branch point to charge), case (4) becomes unchanged, and cases (1)/(2) are unaffected, so every edge strictly between lca and or still nets while lca and above net .
Tested by brute force: random trees, random path-update batches, comparing the DFS-subtree-sum reconstruction against directly adding to every vertex (or edge) on each path.
Subtree updates and queries become ranges after an Euler tour (Euler Tour).
Decision order
- No updates: prefix sums, or a sparse table for idempotent operations.
- All updates before all queries: difference array.
- Queries offline: sort and sweep with a BIT.
- Point update with prefix query: BIT.
- Anything else: segment tree, possibly lazy.
Pitfalls
- Prefix sums of values up to reach : use
ll. - Counting “subarrays with sum divisible by ” with negative numbers: normalise residues into .
- Hash maps keyed by prefix sums are hackable on Codeforces: use a safe hash or sort and compress instead.