Prefix Sum¶
In one sentence
Spend \(O(n)\) once to store running totals pre[i]; after that, the sum of any range \([l, r]\) is one subtraction, pre[r] - pre[l - 1].
1. What problem does it solve?¶
Example — 1873F · Money Trees (5-BinarySearch · I, rating 1300)
\(n\) trees stand in a row; tree \(i\) has \(a_i\) fruits and height \(h_i\). Choose a contiguous group of trees \([l, r]\) such that
- every tree's height is divisible by the next one's: \(h_i\) is divisible by \(h_{i+1}\) for all \(l \le i < r\), and
- the total number of fruits \(a_l + \dots + a_r\) is at most \(k\).
Print the largest possible number of trees \(r - l + 1\) (or \(0\) if no single tree works).
Limits: \(t \le 1000\), \(n \le 2\cdot10^5\) (sum over tests), \(a_i, h_i \le 10^9\), \(k \le 10^9\).
Input Output
5
5 12
3 2 4 1 8
4 4 2 4 1 3
4 8
5 4 1 2
6 2 3 1 2
3 12
7 9 10
2 2 4 1
1 10
11
1 0
7 10
2 6 3 1 5 10 6
72 24 24 12 4 4 2 3
This is exactly the question from our 12 April lesson: for each left end \(l\), find the largest right end \(r\) whose range sum is still within the limit. Two things must be fast:
- "Is the sum of \([l, r]\) at most \(k\)?": adding up \(a_l \dots a_r\) each time costs \(O(n)\). A prefix sum answers it in \(O(1)\).
- "Which \(r\) is the largest?": trying every \(r\) costs \(O(n)\) per \(l\). Since all \(a_i \ge 1\), the sum only grows as \(r\) grows, so binary search finds it in \(O(\log n)\).
The divisibility rule only limits how far right \(r\) may go.
2. The math¶
2.1 Definition¶
With the array 1-indexed and \(\texttt{pre}[0] = 0\):
2.2 The query formula¶
Because pre[0] = 0, the formula also works for \(l = 1\) without a special case.
2.3 Trace on sample 1¶
\(k = 12\), \(a = (3, 2, 4, 1, 8)\), \(h = (4, 4, 2, 4, 1)\).
| \(i\) | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| \(a_i\) | 3 | 2 | 4 | 1 | 8 | |
pre[i] |
0 | 3 | 5 | 9 | 10 | 18 |
| \(h_i\) divisible by \(h_{i+1}\)? | yes (4,4) | yes (4,2) | no (2,4) | yes (4,1) | — | |
far[i] |
3 | 3 | 3 | 5 | 5 |
far[i] is the farthest index we may reach from \(i\) without breaking the divisibility chain. It is filled from right to left: far[n] = n, and far[i] = far[i + 1] if \(h_i\) is divisible by \(h_{i+1}\), otherwise far[i] = i.
For \(l = 1\) we may use \(r \in [1, 3]\): sums pre[r] - pre[0] are \(3, 5, 9\), all \(\le 12\), so \(r = 3\) and the length is 3. For \(l = 4\): \(r \in [4, 5]\), sums \(1\) and \(9\), length \(2\). The answer is \(3\).
2.4 Cost¶
\(O(n)\) for pre and far, then \(O(\log n)\) binary search per \(l\): \(O(n \log n)\) in total. (A two-pointer window can even do it in \(O(n)\); writing both versions once is good practice, as our Search & Ranges recap says.)
3. Lesson notes¶
1. Prefix Sum¶
Idea¶
If you need the sum of many different ranges of a fixed array, do not add the numbers again for each query. Add them once, from left to right, and store the running total.
pre[i] = a[1] + a[2] + ... + a[i]
Then the sum of any range [l, r] is one subtraction:
sum(l, r) = pre[r] - pre[l - 1]
Build it once in O(n). Every query after that is O(1).
Template¶
vector<ll> pre(n + 1, 0);
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + a[i];
}
ll sum = pre[r] - pre[l - 1];
Things to be careful about¶
- Index the array from
1, and makepreof sizen + 1. Thenpre[l - 1]is safe whenl == 1. preshould belong long. The individual numbers may be small, but the total is often not.- Prefix sum only works when the array does not change. If values are updated between queries, this is the wrong tool.
When to reach for it¶
You see repeated questions of the form "what is the sum / count between these two
positions". Counting also works: store 0 and 1 in the array, and the prefix
sum tells you how many 1s are in a range.
4. Worked solution — Money Trees¶
Prefix sum from §3, and "Binary Search Template 1" (largest valid value) from the binary search page, with a check lambda just like on 12 April.
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
void solve() {
int n;
ll k;
cin >> n >> k;
vector<ll> a(n + 1), h(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> h[i];
}
vector<ll> pre(n + 1, 0);
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + a[i];
}
// far[i]: the farthest r such that h[i..r] keeps the divisibility chain
vector<int> far(n + 1);
far[n] = n;
for (int i = n - 1; i >= 1; i--) {
if (h[i] % h[i + 1] == 0) {
far[i] = far[i + 1];
} else {
far[i] = i;
}
}
int ans = 0;
for (int l = 1; l <= n; l++) {
auto check = [&](int r) -> bool {
return pre[r] - pre[l - 1] <= k;
};
int lo = l, hi = far[l], best = -1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (check(mid)) {
best = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
if (best != -1) {
ans = max(ans, best - l + 1);
}
}
cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T = 1;
cin >> T;
for (int i = 0; i < T; i++) {
solve();
}
return 0;
}
5. What can be "prefixed"?¶
Store in pre[i] |
Query | Needs |
|---|---|---|
| sum \(a_1 + \dots + a_i\) | range sum = pre[r] - pre[l-1] |
subtraction undoes addition |
| count of "good" positions (store 1 or 0) | how many good ones in \([l, r]\) | same |
| XOR \(a_1 \oplus \dots \oplus a_i\) | range XOR = pre[r] ^ pre[l-1] |
XOR undoes itself |
| maximum \(\max(a_1, \dots, a_i)\) | only prefixes \([1, r]\) | max cannot be undone, so no general ranges |
6. Common mistakes¶
pre as int
With \(n = 2\cdot10^5\) and \(a_i = 10^9\) the total is \(2\cdot10^{14}\). Use long long.
0-indexed array with pre[l - 1]
If a starts at index 0, pre[l - 1] with \(l = 0\) reads pre[-1]. Index from 1 and make pre size n + 1.
The array changes between queries
A prefix sum is a snapshot. If values are updated after it is built, every later pre[i] is stale.
Binary search when values can be negative
The "largest \(r\)" search only works because every \(a_i \ge 1\) makes the range sum increase with \(r\). With negative values that monotonicity is gone.
7. Practice¶
| Problem | Set | Rating | Idea |
|---|---|---|---|
| 1873F · Money Trees | 5-BinarySearch · I | 1300 | prefix sum + binary search per \(l\) (this page) |
| AIO 2018 · Cloud Coverage | AIO 2018 Q3 | — | positions are prefix sums of the gaps; minimum window |
| AIO 2022 · Spaceship Shuffle | AIO 2022 Q6 | — | prefix sums are the flow on each edge |
| OJ · PYEXAMN | homework 12 Apr | — | longest range with sum \(\le s\), two ways |
Credits & licenses
- §3 joins the Prefix Sum notes of 12 April and section 1 of our Search & Ranges recap (August), unchanged apart from heading levels.
- Example, derivations, trace, worked solution, table and mistakes are ours.