C++ • PROBLEM-SOLVING PATTERNS

Prefix Sums — Use prefix sums for range queries (intro)

Transform O(n) range sum queries into O(1) lookups with a single preprocessing pass.

Historical Context & Motivation

The idea of accumulating partial sums to accelerate repeated queries has deep roots in both mathematics and computer science. Long before the term prefix sum became standard vocabulary in algorithm design, mathematicians relied on cumulative distribution functions and running totals in statistical tables — essentially the same principle applied by hand. In the context of computing, the technique gained prominence as researchers confronted the fundamental tension between preprocessing cost and query-time efficiency. When an application must answer thousands or millions of range queries over a static array, a brute-force summation for each query quickly becomes the bottleneck.

1946
Cumulative Frequency Tables
Statisticians formalize cumulative frequency distributions, pre-computing running sums over binned data to answer range-frequency queries in constant time — a tabular precursor to the prefix-sum array.
1962
Scan Operation in APL
Kenneth Iverson's APL language introduces the scan (prefix) operator, making cumulative reductions a first-class array primitive and popularizing the concept in programming.
1975
Parallel Prefix Algorithms
Ladner and Fischer publish efficient parallel prefix circuits, demonstrating that prefix sums can be computed in O(log n) time on parallel hardware — foundational to GPU computing.
1990s
Competitive Programming & Textbooks
Prefix sums become a staple technique in ICPC and IOI contests. Cormen et al.'s Introduction to Algorithms and similar textbooks codify the pattern as a standard preprocessing strategy for range queries.
2017
C++17 std::inclusive_scan
The C++17 standard library adds std::inclusive_scan and std::exclusive_scan in <numeric>, providing parallel-ready, STL-integrated prefix-sum facilities.

The core question this lesson addresses is straightforward: given an integer array of length n and q queries each requesting the sum of elements in a contiguous subarray, how can we avoid the naïve O(n × q) total cost? The prefix-sum technique provides an elegant answer — O(n) preprocessing followed by O(1) per query — and it forms the conceptual foundation for more advanced structures such as Fenwick trees and segment trees.

Core Principles & Definitions

At its heart, the prefix-sum pattern exploits a property of addition that computer scientists often take for granted: subtraction of overlapping sums isolates any contiguous subarray. By building a single auxiliary array that stores cumulative totals, every subsequent range query reduces to one subtraction. The following cards distill the foundational ideas you need before diving into implementation.

1

Prefix Array Construction

Given array A of length n, construct P where P[i] = A[0] + A[1] + … + A[i]. This single O(n) pass stores every cumulative sum from the start of the array.
2

Range Query Formula

The sum of A[l..r] equals P[r] − P[l−1]. When l = 0, the answer is simply P[r]. This turns every range sum into a constant-time subtraction.
3

Zero-Indexed Convention

In C++ it is common to define P with size n+1 where P[0] = 0, so that sum(l, r) = P[r+1] − P[l]. This avoids the boundary check when l = 0.
4

Time–Space Trade-off

We invest O(n) extra space and O(n) preprocessing time to reduce each query from O(n) to O(1). This trade-off is worthwhile when q ≫ 1.
5

Static Array Assumption

The basic prefix-sum technique assumes no modifications to the original array after preprocessing. Updates require rebuilding the prefix array or upgrading to a Fenwick/segment tree.
✦ KEY TAKEAWAY
Think of the prefix-sum array as a running odometer on a car. To find the distance traveled between mile markers A and B, you subtract the odometer reading at A from the reading at B — no need to re-drive the route. The prefix array records cumulative mileage (sums), and each range query is simply a difference of two readings.

Visual Explanation

Building the Prefix-Sum Array

The top row shows the original array A with indices 0–5. The second row is the prefix-sum array P of length n+1, where P[0] = 0. The highlighted region demonstrates a query for sum(A[1..4]): we read P[5] = 15 and P[1] = 2, then subtract to get 13 in constant time.

Observe that every entry P[i] records the sum of A[0] through A[i−1]. The sentinel value P[0] = 0 ensures the formula sum(l, r) = P[r+1] − P[l] works uniformly, even when the query starts at index 0. This is why the idiomatic C++ implementation allocates a vector of size n + 1 rather than n. The construction loop is straightforward: P[i+1] = P[i] + A[i] for i from 0 to n−1.

Mathematical Framework

Formalizing the prefix-sum construction and query formula clarifies both correctness and complexity. Let A be a zero-indexed array of n integers. We define the prefix-sum array P of length n + 1 as follows.

PREFIX-SUM DEFINITION
P[0] = 0, P[k] = Σ_{i=0}^{k−1} A[i] for k = 1, 2, …, n
P[k] stores the sum of the first k elements of A. The recurrence relation is P[k] = P[k−1] + A[k−1].
RANGE QUERY
sum(l, r) = P[r + 1] − P[l]
Here l and r are 0-based inclusive indices (0 ≤ l ≤ r < n). The derivation follows from telescoping: P[r+1] − P[l] = Σ_{i=0}^{r} A[i] − Σ_{i=0}^{l−1} A[i] = Σ_{i=l}^{r} A[i].
COMPLEXITY
Build: Θ(n) | Query: Θ(1) | Space: Θ(n)
The build phase performs exactly n additions and n+1 writes. Each query performs one subtraction and two array lookups. The auxiliary array P requires n+1 extra words of storage.

A subtle but important numerical consideration arises for large inputs: because the prefix array accumulates sums, P[n] can grow as large as n × max(|A[i]|). For an array of 105 elements each up to 109, P[n] can reach 1014, which overflows a 32-bit integer. In C++ you should use long long (or int64_t) for the prefix array when element magnitudes and array size combine to exceed 231 − 1 ≈ 2.1 × 109.

⚠ Edge Case: Empty Range
Some problem statements allow l > r, which represents an empty subarray with sum 0. Under the formula P[r+1] − P[l], this naturally yields a non-positive difference only if explicitly handled. Define sum(l, r) = 0 when l > r as a guard clause.

C++ Implementation & Breakdown

A clean, idiomatic C++ implementation typically consists of two functions (or a single class): one to construct the prefix array and another to answer queries. Below is the canonical pattern using std::vector<long long> to avoid overflow.

The build function constructs P in a single linear pass. The query function returns the range sum in constant time via P[r+1] − P[l]. The highlighted lines show the two essential operations.

C++17 also provides std::partial_sum (from <numeric>) and std::inclusive_scan / std::exclusive_scan which accomplish the same construction with a single STL call. The choice of exclusive_scan with an initial value of 0 produces the sentinel-based prefix array directly.

💡 STL Alternative
std::exclusive_scan(A.begin(), A.end(), P.begin(), 0LL); produces P[0..n−1] where P[0]=0. Note this writes only n values; you still need P[n] = P[n−1]+A[n−1] if you follow the n+1 convention.

Worked Example

Suppose you are given the array A = [3, −2, 7, 1, 5, −4, 6] (n = 7) and must answer three range-sum queries: sum(0, 3), sum(2, 5), and sum(4, 6). We walk through the complete process below.

Prefix-Sum Construction & Three Queries
1
Step 1 — Allocate P with n + 1 = 8 entriesInitialize P[0] = 0. The array P will have indices 0 through 7.
P = [0, _, _, _, _, _, _, _]
2
Step 2 — Build the prefix arrayIterate i from 0 to 6, computing P[i+1] = P[i] + A[i]: P[1] = 0 + 3 = 3 P[2] = 3 + (−2) = 1 P[3] = 1 + 7 = 8 P[4] = 8 + 1 = 9 P[5] = 9 + 5 = 14 P[6] = 14 + (−4) = 10 P[7] = 10 + 6 = 16
P = [0, 3, 1, 8, 9, 14, 10, 16]
3
Step 3 — Query sum(0, 3)Apply the formula: sum(0, 3) = P[3 + 1] − P[0] = P[4] − P[0] = 9 − 0.
sum(0, 3) = 9 (i.e., 3 + (−2) + 7 + 1 = 9 ✓)
4
Step 4 — Query sum(2, 5)sum(2, 5) = P[6] − P[2] = 10 − 1.
sum(2, 5) = 9 (i.e., 7 + 1 + 5 + (−4) = 9 ✓)
5
Step 5 — Query sum(4, 6)sum(4, 6) = P[7] − P[4] = 16 − 9.
sum(4, 6) = 7 (i.e., 5 + (−4) + 6 = 7 ✓)

All three queries resolved in O(1) each, and the total work was O(n) for the build plus O(1) × 3 = O(3). Had we summed each subarray from scratch, the total would have been O(4 + 4 + 3) = O(11) additions — worse by a factor that grows linearly with the number and size of queries.

Strengths, Limitations & Comparisons

Prefix-sum vs. naïve brute-force for range-sum queries
CriterionPrefix SumBrute Force
PreprocessingO(n)None
Query timeO(1)O(n)
Total for q queriesO(n + q)O(n × q)
Extra spaceO(n)O(1)
Supports updates?No — must rebuildYes — always fresh
Implementation complexityVery lowTrivial

The prefix-sum approach dominates whenever the array is static and the number of queries is non-trivial. Its main limitation — the inability to handle point updates efficiently — is precisely what motivates upgrading to a Fenwick tree (Binary Indexed Tree) which supports both updates and queries in O(log n), or a segment tree which additionally handles non-commutative operations and lazy propagation. Understanding the prefix-sum pattern is therefore not just useful in isolation; it is the conceptual prerequisite for these more powerful structures.

⚙ WHEN TO UPGRADE
If your problem involves interleaved updates and queries on the same array, the prefix-sum array becomes stale after every update. This is the signal to move to a Fenwick tree (O(log n) update + query) or a segment tree (O(log n) with arbitrary associative operations). Think of the prefix-sum array as the 'read-only cache' version — perfect when the data does not change, but requiring a cache-invalidation strategy otherwise.

Connection to Advanced Techniques

The 1-D prefix-sum idea generalizes in several powerful directions. In two dimensions, a 2-D prefix sum (also called a summed-area table or integral image) enables O(1) queries for the sum over any rectangular sub-region of a matrix, at the cost of O(m × n) preprocessing. The technique also extends to prefix XOR, prefix GCD, and prefix products, provided the operation has an inverse (or a suitable cancellation rule). In the competitive programming domain, the difference array is the dual construction: it allows O(1) range updates with a deferred O(n) reconstruction via prefix-sum at the end.

Prefix sums in the landscape of range-query data structures
TechniqueBuildQueryUpdate
1-D Prefix SumO(n)O(1)O(n) rebuild
2-D Prefix SumO(m×n)O(1)O(m×n) rebuild
Difference ArrayO(n)O(n) reconstructO(1) range add
Fenwick Tree (BIT)O(n)O(log n)O(log n)
Segment TreeO(n)O(log n)O(log n)

As you advance through competitive programming and algorithm courses, you will encounter scenarios where the simple prefix-sum array is the optimal tool (static data, sum queries), and others where you need the logarithmic flexibility of tree-based structures. Mastering the prefix-sum pattern first ensures you can recognize when the simpler technique suffices, avoiding unnecessary implementation complexity.

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain why the prefix-sum array P is defined with n + 1 entries (P[0] through P[n]) rather than exactly n entries. What problem does the sentinel P[0] = 0 solve?
PROBLEM 2 — BASIC CALCULATION
Given A = [4, 1, −3, 5, 2], construct the prefix-sum array P and compute sum(1, 3).
PROBLEM 3 — INTERMEDIATE
You are given an array of n = 10⁶ integers, each in the range [−10⁹, 10⁹], and 10⁵ range-sum queries. A teammate proposes using int (32-bit) for the prefix array. Identify the potential issue, quantify the maximum possible prefix value, and state the correct data type in C++.
PROBLEM 4 — APPLIED
A network monitor records packets-per-second in a circular buffer of size n. An analyst issues q queries of the form "total packets between second l and second r." The buffer is filled once and then queried without modification. Write C++ code (using vectors) that preprocesses the buffer and answers each query in O(1). Assume 0-indexed, inclusive ranges.
PROBLEM 5 — CRITICAL THINKING
Prove that the prefix-sum query formula sum(l, r) = P[r+1] − P[l] correctly computes Σ_{i=l}^{r} A[i] for all 0 ≤ l ≤ r < n. Then explain how the technique breaks down if the operation is changed from addition to maximum (i.e., range-max queries). What structural property of addition does range-max lack?

Summary

The prefix-sum pattern transforms repeated range-sum queries on a static array from O(n) per query to O(1) per query after a single O(n) preprocessing pass. The auxiliary array P of size n+1 stores cumulative sums, with the sentinel P[0] = 0 ensuring the formula sum(l, r) = P[r+1] − P[l] applies uniformly to all valid ranges.

Key implementation concerns include using long long to prevent overflow and recognizing that this technique assumes a static (non-updating) array. When updates are required, the natural next step is to study Fenwick trees and segment trees, both of which build on the cumulative-sum intuition established here. The pattern also extends to 2-D prefix sums (summed-area tables) and difference arrays for range-update problems.

Varsity Tutors • C++ • Prefix Sums — Use prefix sums for range queries (intro)