【一维 前缀和+差分】
一、一维前缀和
1.1 定义
给定一个数组 a[1..n]
,其前缀和数组 pre[1..n]
定义为:
pre[i]=a[1]+a[2]+⋯+a[i] pre[i] = a[1] + a[2] + \dots + a[i] pre[i]=a[1]+a[2]+⋯+a[i]
即 pre[i]
表示原数组从第 1 项到第 i 项的和。
1.2 构建
int a[N], pre[N];
for (int i = 1; i <= n; i++)
{pre[i] = pre[i - 1] + a[i];
}
1.3 区间求和
使用前缀和可以在 O(1)O(1)O(1) 时间内求任意区间 [l,r][l, r][l,r] 的和:
sum=pre[r]−pre[l−1] sum = pre[r] - pre[l - 1] sum=pre[r]−pre[l−1]
公式推导:
pre[r]=a[1]+a[2]+⋯+a[l−1]+a[l]+⋯+a[r]pre[l−1]=a[1]+a[2]+⋯+a[l−1] pre[r] = a[1] + a[2] + \dots + a[l-1] + a[l] + \dots + a[r] \\ pre[l-1] = a[1] + a[2] + \dots + a[l-1] pre[r]=a[1]+a[2]+⋯+a[l−1]+a[l]+⋯+a[r]pre[l−1]=a[1]+a[2]+⋯+a[l−1]
所以两者一减,刚好剩下区间 [l,r][l, r][l,r] 的和。
1.4 应用场景
- 快速计算区间和
- 优化暴力 O(n2)O(n^2)O(n2) 的区间统计问题为 O(n)O(n)O(n)
1.5 示例
// 输入一个数组,求多个区间 [l, r] 的和
int a[N], pre[N];
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i];while (q--)
{int l, r;cin >> l >> r;cout << pre[r] - pre[l - 1] << '\n';
}
二、一维差分数组
2.1 定义
对于原数组 a[1..n]
,其差分数组 diff[1..n]
定义为:
diff[i]=a[i]−a[i−1] (i≥2), diff[1]=a[1] diff[i] = a[i] - a[i - 1]\ \ (i \geq 2),\ \ diff[1] = a[1] diff[i]=a[i]−a[i−1] (i≥2), diff[1]=a[1]
2.2 构建
int a[N], diff[N];
diff[1] = a[1];
for (int i = 2; i <= n; i++)
{diff[i] = a[i] - a[i - 1];
}
2.3 区间加法操作
若想对区间 [l,r][l, r][l,r] 所有数加上 ddd,只需:
diff[l] += d;
diff[r + 1] -= d;
原理在于:差分数组记录的是“增量”,只需要在区间起点加一个数、在区间终点的下一位减去这个数,就能确保中间所有位置都累加这个值。
然后通过前缀和还原整个数组:
a[1] = diff[1];
for (int i = 2; i <= n; i++)
{a[i] = a[i - 1] + diff[i];
}
2.4 区间减法操作
若想对区间 [l,r][l, r][l,r] 所有数减去 ddd,也可以使用差分数组:
diff[l] -= d;
diff[r + 1] += d;
原理类似:差分数组记录的是“增量”,如果我们想对一个区间 [l,r][l, r][l,r] 的所有元素减去 ddd,只需要在区间起点加上 −d-d−d,在区间终点的下一位加上 +d+d+d,就能确保整个区间内的值都减少 ddd,而其他位置不受影响。这与区间加法操作完全对称,只是将 ddd 替换为 −d-d−d。
三、差分原理
3.1 目的
差分数组的核心目的是:将原本需要 O(n) 的区间修改操作优化为 O(1)。
比如:我们想对数组中一段区间 [l,r][l, r][l,r] 的所有元素都加上一个数 ddd,若用原数组暴力加法,每次操作就要 O(r−l+1)O(r-l+1)O(r−l+1);当有 mmm 次操作时,总复杂度是 O(mn)O(mn)O(mn),会超时。
3.2 差分数组的构建
我们构建一个数组 diff[1…n]diff[1 \ldots n]diff[1…n]:
diff[i]=a[i]−a[i−1] diff[i] = a[i] - a[i - 1] diff[i]=a[i]−a[i−1]
也可以理解为:
差分数组记录的是原数组中相邻两项之间的“变化量”。
根据这个定义,有:
a[i]=diff[1]+diff[2]+⋯+diff[i]⇒a[i]=∑j=1idiff[j] a[i] = diff[1] + diff[2] + \dots + diff[i] \Rightarrow a[i] = \sum_{j=1}^{i} diff[j] a[i]=diff[1]+diff[2]+⋯+diff[i]⇒a[i]=j=1∑idiff[j]
也就是说,原数组可以通过差分数组做前缀和来还原。
3.3 差分操作的核心逻辑
想要将区间 [l,r][l, r][l,r] 所有数都加上 ddd,我们可以在 diff[]
上做如下处理:
diff[l] += d;
diff[r + 1] -= d;
为什么这样就能完成整个区间的加法?
我们回忆差分的“还原”过程:
a[1] = diff[1];
a[2] = a[1] + diff[2];
a[3] = a[2] + diff[3];
...
你会发现,前缀相加会让 diff[l]
的影响一直传导下去。直到 diff[r+1]
把这部分影响抵消为止。
也就是说:
- a[l]a[l]a[l] 到 a[r]a[r]a[r] 都被加上了 ddd
- 而 a[r+1]a[r+1]a[r+1] 之后的数不受影响
这就实现了:O(1) 修改一个区间
四、一个形象的类比
假设你站在楼梯上,从第 1 级楼梯往上走,
a[i]
是你在第i
级台阶上的高度。
- 前缀和:相当于你每走一步都记一下总共走了多远。
- 差分数组:相当于你记录每一步你上升(或下降)了多少。
你只要知道每一步的变化量(差分数组),就能还原出每一级台阶的实际高度(原数组);你也能知道在一段范围内你升高了多少(前缀和)。
五、前缀和与差分的区别与联系
类别 | 作用 | 技术本质 | 时间复杂度 |
---|---|---|---|
前缀和 | 快速查询区间和 | 把每个位置存成前缀累加和 | 查询 O(1)O(1)O(1),构建 O(n)O(n)O(n) |
差分数组 | 快速区间修改 | 存储相邻值之间的“变化” | 修改 O(1)O(1)O(1),恢复 O(n)O(n)O(n) |
它们的关系就像:
- 前缀和是累计的“总和”
- 差分是相邻的“变化量”
差分数组其实就是前缀和的逆操作。
六、常见问题汇总
-
差分数组的还原为什么用前缀和?
差分数组记录的是相邻元素之间的增量,所以前缀和就是原数组。
-
差分数组是否支持区间乘法?
不支持,差分只能处理区间加减。
-
差分数组是否可以从
0
开始?可以,但要注意下标对应的意义,最好从 1 开始更清晰。
-
是否每次都需要重建差分数组?
看情况。如果每次操作互不干扰,可以复用;否则建议重建或静态开数组并清空。
七、总结
- 前缀和用于高效区间查询
- 差分数组用于高效区间修改
- 两者配合使用是处理“区间加减 + 查询”类问题的利器
在处理数据量较大的题目(如 10510^5105 或 10610^6106)时,前缀和与差分是比线段树更快、更简洁的选择。