中 · 前缀和
区间求和
每个区间都现场加一遍,每次都要 O(区间长度),而问题会一直来。前缀和把所有工作挪到一次预处理:prefix[i] 是前 i 个数的和,于是任何区间都变成一次减法。
一句话答案先用一遍算出 <code>prefix[i] = prefix[i-1] + a[i]</code>(i 从 1 到 n)。区间 [l, r] 的答案就是 <code>prefix[r] - prefix[l-1]</code> —— 每次查询一次减法,总和可达 10^13,要用 <code>long long</code>。
题目
输入: 第一行是 n 和 q(1 ≤ n ≤ 10000,1 ≤ q ≤ 10000)。第二行 n 个整数(−10^9 ≤ a_i ≤ 10^9)。接下来 q 行,每行 l 和 r(1 ≤ l ≤ r ≤ n)。
输出: q 行:每个查询输出 a[l..r] 的和,一行一个。
样例:
| 输入 | 输出 |
|---|---|
5 3 | 15 |
从 1 开始计数的约定在这里悄悄干活:prefix[0] = 0 让公式在 l = 1 时也不用特判。差一错误就住在这条缝里 —— 见下面的错误示例。
你的解答
「运行我的输入」用的自定义输入
判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。
卡住了?提示
提示 1 — 让 prefix[i] 把 a[i] 也算进去
把含义定成 prefix[i] = a[1] + … + a[i],并令 prefix[0] = 0。于是 a[l..r] 的和自动裂项:到 r 为止的全部,减去 l 之前的全部。这题一半的差一错误,都来自两种约定混着用。
提示 2 — 数组从 1 数,而 C++ 从 0 数
把数组开大一格,直接不用第 0 格(或把 0 存进去),让 a[1..n] 和 prefix[1..n] 停在自然下标上。在代码里到处撒 - 1 去和约定搏斗,正是符号错误的诞生方式。
提示 3 — int 装不下这些和
单个元素装得进 int(最多 10^9),但 n 个加起来是 10^13 —— 远超 int 约 2.1 × 10^9 的天花板。把 prefix 声明成 long long。溢出不会崩溃,只会悄悄绕回一个错误的数 —— 这是最阴险的一类 bug。
题解(先自己试试!)
#include <iostream>
using namespace std;
long long prefix[10005];
int main() {
int n, q;
cin >> n >> q;
prefix[0] = 0;
for (int i = 1; i <= n; i++) {
long long x;
cin >> x;
prefix[i] = prefix[i - 1] + x;
}
for (int k = 0; k < q; k++) {
int l, r;
cin >> l >> r;
cout << prefix[r] - prefix[l - 1] << endl;
}
return 0;
}
前缀和是你第一次在知道问题之前就先把结构建好的做法 —— 同一个想法在 最大子段和 里还会回来:用前缀之差一趟扫出最优窗口。当查询之外还要修改,下一个工具是差分数组,再往后是树。
深度解析
为什么这样做对
把「跑动中的总和」存下来。prefix[r] - prefix[l-1] 会抵消两个前缀共有的部分,剩下的恰好是 a[l] + … + a[r]。和筛法、计数排序的桶一样,都是先把结构建好、再用差值回答。
代价
建表 O(n),每次查询 O(1),空间 O(n)。暴力是 O(n·q) —— n = q = 10000 时是 1 亿次加法对 2 万次的差别。
常见错法
- 答成
prefix[r] - prefix[l]—— 悄悄丢掉了 a[l]。公式是prefix[l-1],只用 l = 1 的测试会把这个 bug 藏起来。 - prefix 用
int存:单个元素装得下,总和装不下。溢出悄悄回绕,答案只是看起来像对的。
下一步
「跑动总和」的技巧在 最大子段和 里以 Kadane 洞察的名义回归 —— 最优窗口就是两个前缀之差。
记住这两句
- prefix[i] = a[1] + … + a[i];区间 = prefix[r] − prefix[l−1]。
- 很多 int 相加要用 long long —— 溢出是悄悄发生的。
🐞 这段是错的 —— 点开,亲手让它挂一次
答的是 prefix[r] - prefix[l] —— 悄悄丢掉了 a[l]
#include <iostream>
using namespace std;
long long prefix[10005];
int main() {
int n, q;
cin >> n >> q;
prefix[0] = 0;
for (int i = 1; i <= n; i++) {
long long x;
cin >> x;
prefix[i] = prefix[i - 1] + x;
}
for (int k = 0; k < q; k++) {
int l, r;
cin >> l >> r;
cout << prefix[r] - prefix[l] << endl;
}
return 0;
}