前缀和与差分 简介

Aerhuo 发布于 2025-10-09 53 次阅读


前缀和与差分是处理数组问题时最基础、最强大的两种预处理思想。它们互为逆运算,一个为“区间查询”而生,一个为“区间修改”而生,共同构成了处理静态数组问题的基石。


前缀和:秒速区间查询

1. 核心思想

前缀和的核心思想是 “用空间换时间”。我们通过一次性的预处理,创建一个辅助数组,记录下原始数组从开头到当前位置的累计和。这个“记总账”的行为,使得后续任意区间的求和查询都可以在瞬间完成。

2. 用途与优势

  • 唯一用途:对静态数组进行频繁的区间和查询
  • 巨大优势:将区间和查询的时间复杂度从 O(N) 优化到了极致的 O(1)。在需要进行大量查询的场景下,这是质的飞跃。

3. 实现方法

a. 构建前缀和数组 prefix
创建一个比原始数组 a 长度多 1 的 prefix 数组,并令 prefix[0] = 0(这个“哨兵”可以简化查询逻辑)。
prefix[i] 存储的是 a[0]a[i-1] 的和。

// 原始数组 a = {a_0, a_1, ..., a_{n-1}}
std::vector<long long> prefix(n + 1, 0);
for (int i = 0; i < n; ++i) {
    prefix[i + 1] = prefix[i] + a[i];
}

b. 进行区间查询
要计算原始数组 a 在闭区间 [l, r] 的和,使用以下公式:

sum(l, r) = prefix[r + 1] - prefix[l]

4. 劣势

  • 只适用于静态数组:如果原始数组的任何一个元素发生改变,那么这个元素之后的所有前缀和都需要 O(N) 的时间重新计算,预处理的优势荡然无存。

差分:闪电区间修改

1. 核心思想

差分是前缀和的逆运算,它的核心思想是 “延迟计算”“影响标记”。它不记录每个元素的具体值,而是记录当前元素与前一个元素的差值。通过这种方式,对一个区间的整体增减操作,可以巧妙地转化为只对区间两个端点的“影响标记”进行修改。

2. 用途与优势

  • 唯一用途:对静态数组进行频繁的区间整体增/减操作。
  • 巨大优势:将区间修改的时间复杂度从 O(N) 优化到了极致的 O(1)

3. 实现方法

a. 构建差分数组 diff
diff[i] 存储的是 a[i] - a[i-1](规定 a[-1]=0)。

// 原始数组 a = {a_0, a_1, ..., a_{n-1}}
std::vector<int> diff(n);
diff[0] = a[0];
for (int i = 1; i < n; ++i) {
    diff[i] = a[i] - a[i-1];
}

b. 进行区间修改
要给原始数组 a 的闭区间 [l, r] 上的每个元素都加上 value,只需修改差分数组的两个位置:

diff[l] += value;
if (r + 1 < n) {
    diff[r + 1] -= value;
}

第一个操作 diff[l] += value 使得从 l 开始的所有元素在求前缀和时都会增加 value。第二个操作则是在 r+1 的位置把这个影响抵消掉,保证修改只在 [l, r] 区间内生效。

c. 还原最终数组
在完成所有修改后,如果需要得到最终的数组 a,只需对差分数组 diff 求一次前缀和即可。

a[0] = diff[0];
for (int i = 1; i < n; ++i) {
    a[i] = a[i-1] + diff[i];
}

4. 劣势

  • 查询低效:在完成所有 O(1) 的修改后,如果想查询任何一个位置或区间的具体值,都必须先花费 O(N) 的时间对差分数组求前缀和来还原整个数组。

总结

特性 前缀和 差分
核心 预处理累计和 记录相邻元素的差
超能力 O(1) 区间查询 O(1) 区间修改
软肋 修改操作慢 O(N) 查询操作慢 O(N)
关系 差分数组的前缀和是原数组 原数组的差分是差分数组
适用场景 查询远多于修改 修改远多于查询(且查询集中在最后)

前缀和与差分是解决数组问题的“阴”与“阳”,理解它们的互补关系,是掌握更高级数据结构(如树状数组、线段树,它们同时优化了查询和修改)的坚实基础。