#LC303. 静态数组区间求和 (前缀和模板)
静态数组区间求和 (前缀和模板)
[OI 基础训练] 静态数组区间求和 (前缀和模板)
一、 题目背景与知识点讲解
在信息学竞赛中,我们经常遇到需要对一个数组进行多次查询的情况。
场景描述:
假设你有一个数组 nums,它的内容是固定不变的(即“Immutable”)。现在有大量的查询请求,每个请求都会给出一个区间 [left, right],要求你计算这个区间内所有元素的和。
1. 暴力解法 (Naive Approach)
最直观的方法是对于每一次查询,都写一个 for 循环从 left 遍历到 right 进行累加。
- 时间复杂度:如果是 次查询,数组长度为 ,每次查询最坏情况是 。总时间复杂度为 。
- 后果:当 和 都达到 级别时,运算量会达到 ,导致程序超时 (Time Limit Exceeded)。
2. 优化解法:前缀和 (Prefix Sum)
既然数组不会变,我们可以运用**“预处理”**的思想,用空间换时间。
我们定义一个辅助数组 preSum,其中 preSum[i] 表示原数组前 i 个元素的总和。
-
定义:
- 设原数组为 (为了方便计算,通常下标从 1 开始)。
- 。
- 特别地,定义 。
-
递推公式:
-
区间求和公式: 如果要求区间 的和(即 ),我们可以利用前缀和数组快速计算:
- 解释:前 个数的和,减去前 个数的和,剩下的就是第 到第 个数的和。
-
复杂度分析:
- 预处理时间:遍历一次数组,复杂度 。
- 查询时间:利用公式直接计算,复杂度 。
- 总时间复杂度:。这是一个巨大的提升。
二、 题目描述
给定一个包含 个整数的数组 。该数组在后续过程中不会发生改变。 接下来会有 次查询,每次查询给定两个整数 和 ,请你计算并输出数组中从下标 到下标 (包含 和 )的所有元素之和。
为了方便处理边界情况,本题约定数组下标从 $1$ 开始计数。
输入格式
第一行包含两个整数 和 ,分别表示数组元素的个数和查询的次数。 第二行包含 个整数 ,表示数组的元素。 接下来 行,每行包含两个整数 和 ,表示查询的区间范围。
输出格式
对于每一次查询,输出一行一个整数,表示该区间的和。
数据范围
- 结果保证在 32 位有符号整数范围内(但在竞赛中建议使用
long long以防万一)。
样例输入
6 3
-2 0 3 -5 2 -1
1 3
3 6
1 6
样例输出
1
-1
-3
样例解释
- 数组为
[-2, 0, 3, -5, 2, -1](1-based)。 - 查询 [1, 3]:。
- 查询 [3, 6]:。
- 查询 [1, 6]:所有元素之和为 -3。
三、 标准代码 (C++14 OI风格)
/**
* 题目:静态数组区间求和
* 知识点:前缀和 (Prefix Sum)
* 时间复杂度:预处理 O(N), 查询 O(1), 总计 O(N+M)
* 空间复杂度:O(N)
*/
#include <iostream>
#include <vector>
using namespace std;
// 定义长整型,防止累加过程中溢出(虽然本题数据范围int够用,但养成好习惯)
typedef long long ll;
const int MAXN = 100005;
// 在全局定义数组,防止栈溢出,且自动初始化为0
// preSum[i] 存储 A[1]...A[i] 的和
ll preSum[MAXN];
int main() {
// 1. IO 读写优化,竞赛必备,加快 cin/cout 速度
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
if (!(cin >> n >> m)) return 0;
// 2. 读入数据并预处理前缀和
// 技巧:我们不需要存储原数组 nums,直接边读边计算 preSum 即可节省空间
// preSum[0] 默认为 0,符合逻辑
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
// 核心递推公式:当前前缀和 = 上一个前缀和 + 当前数值
preSum[i] = preSum[i - 1] + x;
}
// 3. 处理 M 次查询
for (int k = 0; k < m; ++k) {
int l, r;
cin >> l >> r;
// 4. O(1) 输出结果
// 核心查询公式:Sum(L, R) = preSum[R] - preSum[L-1]
cout << preSum[r] - preSum[l - 1] << "\n";
}
return 0;
}
四、 教学备课重点 (教练参考)
- 下标处理:
- LeetCode303 原题是 0-indexed(下标从0开始),公式是
sumRange(i, j) = preSum[j+1] - preSum[i]。 - 在 OI 中,为了避免处理
L-1变成 -1 的越界情况,通常建议让数组下标从 1 开始。这样preSum[0]天然就是 0,代码逻辑更统一(即preSum[R] - preSum[L-1])。
- LeetCode303 原题是 0-indexed(下标从0开始),公式是
- 不可变性 (Immutable):
- 向学生强调,这种 查询的前提是数组不修改。如果数组元素频繁修改,前缀和数组就需要重新计算(),此时需要更高级的数据结构(如树状数组或线段树)。
- 空间换时间:
- 讲解前缀和的本质是缓存。我们把计算过的结果存起来,下次直接用减法还原。这是算法设计中非常核心的思想。
- 溢出风险:
- 虽然单个元素很小,但 个元素累加可能会超过
int范围。虽然本题数据范围限制了总和在int内,但建议学生在涉及“求和”类题目时优先考虑long long。
- 虽然单个元素很小,但 个元素累加可能会超过