前缀和与差分
前缀和与差分算法详解
前缀和
前缀和(Prefix Sum)是一种高效的数组预处理技巧,核心是通过构建“前缀和数组”,将区间和计算从传统的线性时间(O(n))优化为常数时间(O(1))。其本质是“预存储前i个元素的累加和”,后续查询区间和时直接通过查表推导,避免重复计算。
适用场景
- 频繁查询数组中任意区间的元素和(如子数组和、子矩阵和);
- 解决与区间和相关的衍生问题(如子数组和的最大值、平均值、是否存在目标和等)。
差分
差分(Difference)是与前缀和互补的数组预处理技巧,核心是通过构建“差分数组”,将区间更新操作(如给某区间所有元素加/减一个值)从线性时间(O(n))优化为常数时间(O(1))。其本质是“记录相邻元素的差值”,区间更新时仅需修改差分数组的两个端点,最后通过前缀和还原出原数组。
关键性质
- 差分数组的前缀和等于原数组;
- 原数组的相邻元素差值等于差分数组(即差分是前缀和的逆操作)。
适用场景
- 频繁对数组的任意区间进行统一更新(如给 [x,y] 区间所有元素加 z);
- 结合前缀和解决“区间更新+区间查询”的复合问题。
一维数组的前缀和与差分
一维数组是前缀和与差分的基础场景,所有操作围绕“线性结构”展开,逻辑清晰且易于理解。
一维数组前缀和
核心定义
设原始一维数组为 a[n](下标从 1 开始,a[0] = 0,避免边界判断),其前缀和数组 sum[n] 定义为:
sum[i] = a[1] + a[2] + ... + a[i]
递推公式:sum[i] = sum[i-1] + a[i](前i个元素的和 = 前i-1个元素的和 + 第i个元素)。
示例与代码实现
以数组 a = [0, 1, 2, 3, 4, 5](a[0]=0,有效元素为 a[1]~a[5])为例,前缀和数组 sum 的计算过程如下:
| 下标 i | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| a[i] | 0 | 1 | 2 | 3 | 4 | 5 |
| sum[i] | 0 | 1(sum[0]+a[1]) | 3(sum[1]+a[2]) | 6(sum[2]+a[3]) | 10(sum[3]+a[4]) | 15(sum[4]+a[5]) |
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
// 原始数组:a[0]=0(占位),a[1]~a[5]为有效元素
int a[6] = { 0, 1, 2, 3, 4, 5 };
// 前缀和数组:sum[0]=0,sum[i]存储a[1]~a[i]的和
int sum[6];
sum[0] = a[0]; // sum[0] = 0
// 计算前缀和(递推公式)
for (int i = 1; i <= 5; i++) {
sum[i] = sum[i - 1] + a[i];
}
// 输出结果
cout << "一维数组a(有效元素):";
for (int i = 1; i <= 5; i++) {
cout << a[i] << ' ';
}
cout << "\n一维前缀和sum(有效元素):";
for (int i = 1; i <= 5; i++) {
cout << sum[i] << ' ';
}
return 0;
}
运行结果:
一维数组a(有效元素):1 2 3 4 5
一维前缀和sum(有效元素):1 3 6 10 15
简单应用:查询区间和
需求:给定数组 a,快速计算区间 [r, l](从第r个元素到第l个元素)的和。
核心公式:区间和 = sum[l] - sum[r-1]
(解释:sum[l] 是 a[1]~a[l] 的和,sum[r-1] 是 a[1]~a[r-1] 的和,两者相减即得到 a[r]~a[l] 的和)。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
int a[6] = { 0, 1, 2, 3, 4, 5 };
int sum[6];
sum[0] = a[0];
// 1. 预处理前缀和数组
for (int i = 1; i <= 5; i++) {
sum[i] = sum[i - 1] + a[i];
}
// 2. 输入查询区间 [r, l]
int r, l;
cout << "输入数组区间[r, l](r ≤ l):";
cin >> r >> l;
// 3. 计算并输出区间和
int intervalSum = sum[l] - sum[r - 1];
cout << "数组在区间[" << r << "," << l << "]的和:" << intervalSum;
return 0;
}
测试案例:
- 输入:
2 4(查询a[2]~a[4]的和:2+3+4=9); - 运行结果:
数组在区间[2,4]的和:9。
一维数组差分
核心定义
设原始一维数组为 a[n](下标从 1 开始,a[0] = 0),其差分数组 diff[n] 定义为:
diff[i] = a[i] - a[i-1](第i个元素与第i-1个元素的差值)。
示例与代码实现
以数组 a = [0, 1, 2, 3, 4, 5] 为例,差分数组 diff 的计算过程如下:
| 下标 i | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| a[i] | 0 | 1 | 2 | 3 | 4 | 5 |
| diff[i] | 0 | 1-0=1 | 2-1=1 | 3-2=1 | 4-3=1 | 5-4=1 |
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
// 原始数组:a[0]=0,a[1]~a[5]为有效元素
int a[6] = { 0, 1, 2, 3, 4, 5 };
// 差分数组:diff[0]=0,diff[i] = a[i] - a[i-1]
int diff[6];
diff[0] = a[0]; // diff[0] = 0
// 计算差分数组
for (int i = 1; i <= 5; i++) {
diff[i] = a[i] - a[i - 1];
}
// 输出结果
cout << "一维数组a(有效元素):";
for (int i = 1; i <= 5; i++) {
cout << a[i] << ' ';
}
cout << "\n一维差分数组diff(有效元素):";
for (int i = 1; i <= 5; i++) {
cout << diff[i] << ' ';
}
return 0;
}
运行结果:
一维数组a(有效元素):1 2 3 4 5
一维差分数组diff(有效元素):1 1 1 1 1
简单应用:区间更新与原数组还原
需求:给数组 a 的区间 [x, y] 内所有元素增加一个值 z,然后还原出更新后的数组 a。
传统方法:遍历 [x, y] 区间,逐个给元素加 z,时间复杂度 O(y-x+1);
差分方法:仅需修改差分数组的两个端点,时间复杂度 O(1),再通过差分数组的前缀和还原原数组。
核心操作(区间更新)
diff[x] += z:表示从第x个元素开始,所有后续元素都增加z;diff[y+1] -= z:表示从第y+1个元素开始,抵消前面增加的z(避免影响区间外的元素)。
原数组还原
更新差分数组后,对 diff 求前缀和,即可得到更新后的原数组 a,公式为:a[i] = a[i-1] + diff[i]。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
// 原始数组:a[0]=0,a[1]~a[5]为有效元素
int a[6] = { 0, 1, 2, 3, 4, 5 };
// 差分数组
int diff[6];
diff[0] = a[0];
// 1. 预处理差分数组
for (int i = 1; i <= 5; i++) {
diff[i] = a[i] - a[i - 1];
}
// 2. 输入区间更新参数:[x,y] 区间加 z
int x, y, z;
cout << "输入数组区间[x,y](x ≤ y)及需要加的z值:";
cin >> x >> y >> z;
// 3. 差分数组更新(仅修改两个端点)
diff[x] += z;
if (y + 1 <= 5) { // 避免越界(若y是最后一个元素,y+1超出数组范围,无需操作)
diff[y + 1] -= z;
}
// 4. 从差分数组还原更新后的原数组
for (int i = 1; i <= 5; i++) {
a[i] = a[i - 1] + diff[i];
}
// 5. 输出结果
cout << "更新后的一维数组a(有效元素):";
for (int i = 1; i <= 5; i++) {
cout << a[i] << ' ';
}
return 0;
}
测试案例:
- 输入:
2 4 1(给a[2]~a[4]加 1,即 2→3、3→4、4→5); - 运行结果:
更新后的一维数组a(有效元素):1 3 4 5 5。
二维数组的前缀和与差分
二维数组(矩阵)的前缀和与差分是一维场景的扩展,核心是处理“子矩阵和”与“子矩阵更新”,逻辑上需要考虑行和列两个维度的叠加。
二维数组前缀和
核心定义
设原始二维数组为 a[n][m](下标从 1 开始,a[0][j] = a[i][0] = 0,避免边界判断),其二维前缀和数组 sum[n][m] 定义为:
sum[i][j] = 以 (1,1) 为左上角、(i,j) 为右下角的子矩阵的所有元素和。
递推公式与原理
二维前缀和的计算需要考虑“行前缀和”与“列前缀和”的重叠部分,递推公式为:
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j]
原理图解:
sum[i-1][j]:上方子矩阵的和(行 1~i-1,列 1~j);sum[i][j-1]:左侧子矩阵的和(行 1~i,列 1~j-1);sum[i-1][j-1]:上方与左侧重叠的子矩阵的和(被重复加了两次,需减去一次);a[i][j]:当前单元格的值(最后加上,构成完整的(1,1)~(i,j)子矩阵)。
示例与代码实现
以 3×3 矩阵 a(a[0][*] = a[*][0] = 0)为例,前缀和数组 sum 的计算过程如下:
原始数组 a(有效区域:a[1][1]~a[3][3]):
| a[i][j] | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 2 | 3 |
| 2 | 0 | 2 | 1 | 3 |
| 3 | 0 | 3 | 2 | 1 |
前缀和数组 sum:
| sum[i][j] | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1(sum[0][1]+sum[1][0]-sum[0][0]+a[1][1]) | 3(sum[0][2]+sum[1][1]-sum[0][1]+a[1][2]) | 6(sum[0][3]+sum[1][2]-sum[0][2]+a[1][3]) |
| 2 | 0 | 3(sum[1][1]+sum[2][0]-sum[1][0]+a[2][1]) | 6(sum[1][2]+sum[2][1]-sum[1][1]+a[2][2]) | 12(sum[1][3]+sum[2][2]-sum[1][2]+a[2][3]) |
| 3 | 0 | 6(sum[2][1]+sum[3][0]-sum[2][0]+a[3][1]) | 11(sum[2][2]+sum[3][1]-sum[2][1]+a[3][2]) | 18(sum[2][3]+sum[3][2]-sum[2][2]+a[3][3]) |
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
// 关闭同步流,加速输入输出(竞赛常用)
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// 原始二维数组:a[0][*] = a[*][0] = 0(占位)
int a[4][4] = {
{0, 0, 0, 0},
{0, 1, 2, 3},
{0, 2, 1, 3},
{0, 3, 2, 1}
};
// 二维前缀和数组:sum[0][*] = sum[*][0] = 0
int sum[4][4] = { 0 };
// 计算二维前缀和(递推公式)
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
}
}
// 输出原始数组和前缀和数组(验证)
cout << "原始数组a(有效区域):\n";
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << a[i][j] << " ";
}
cout << '\n';
}
cout << "二维前缀和数组sum(有效区域):\n";
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << sum[i][j] << " ";
}
cout << '\n';
}
return 0;
}
运行结果:
原始数组a(有效区域):
1 2 3
2 1 3
3 2 1
二维前缀和数组sum(有效区域):
1 3 6
3 6 12
6 11 18
简单应用:查询子矩阵和
需求:给定二维数组 a,快速计算以 (x1,y1) 为左上角、(x2,y2) 为右下角的子矩阵的和。
核心公式:
子矩阵和 = sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1]
原理图解:
sum[x2][y2]:(1,1)~(x2,y2)的完整子矩阵和;sum[x2][y1-1]:(1,1)~(x2,y1-1)的左侧子矩阵和(需减去);sum[x1-1][y2]:(1,1)~(x1-1,y2)的上方子矩阵和(需减去);sum[x1-1][y1-1]:(1,1)~(x1-1,y1-1)的重叠子矩阵和(被重复减了两次,需加回一次)。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// 1. 初始化原始数组和前缀和数组
int a[4][4] = {
{0, 0, 0, 0},
{0, 1, 2, 3},
{0, 2, 1, 3},
{0, 3, 2, 1}
};
int sum[4][4] = { 0 };
// 2. 计算二维前缀和
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
}
}
// 3. 输入子矩阵的对角坐标 (x1,y1)(左上)和 (x2,y2)(右下)
int x1, y1, x2, y2;
cout << "输入子矩阵坐标 (x1,y1) 和 (x2,y2)(x1 ≤ x2,y1 ≤ y2):";
cin >> x1 >> y1 >> x2 >> y2;
// 4. 计算并输出子矩阵和
int matrixSum = sum[x2][y2] - sum[x2][y1 - 1] - sum[x1 - 1][y2] + sum[x1 - 1][y1 - 1];
cout << "子矩阵 (" << x1 << "," << y1 << ")~(" << x2 << "," << y2 << ") 的和:" << matrixSum;
return 0;
}
测试案例:
- 输入:
1 1 3 2(子矩阵为a[1][1]~a[3][2],元素为 1、2、2、1、3、2,和为 11); - 运行结果:
子矩阵 (1,1)~(3,2) 的和:11。
二维数组差分
核心定义
设原始二维数组为 a[n][m](下标从 1 开始,a[0][j] = a[i][0] = 0),其二维差分数组 diff[n][m] 定义为:
diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
(本质是二维前缀和的逆操作:差分数组的二维前缀和等于原数组)。
示例与代码实现
以 3×3 矩阵 a 为例,差分数组 diff 的计算过程如下:
原始数组 a(有效区域:a[1][1]~a[3][3]):
| a[i][j] | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 2 | 3 |
| 2 | 0 | 2 | 1 | 3 |
| 3 | 0 | 3 | 2 | 1 |
差分数组 diff(按公式计算):
| diff[i][j] | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1-0-0+0=1 | 2-0-1+0=1 | 3-0-2+0=1 |
| 2 | 0 | 2-1-0+0=1 | 1-2-2+1=-2 | 3-3-1+2=1 |
| 3 | 0 | 3-2-0+0=1 | 2-3-1+2=0 | 1-3-2+1=-3 |
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// 原始二维数组:a[0][*] = a[*][0] = 0
int a[4][4] = {
{0, 0, 0, 0},
{0, 1, 2, 3},
{0, 2, 1, 3},
{0, 3, 2, 1}
};
// 二维差分数组:diff[0][*] = diff[*][0] = 0
int diff[4][4] = { 0 };
// 计算二维差分数组(公式:diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1])
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
diff[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];
}
}
// 输出结果
cout << "原始数组a(有效区域):\n";
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << a[i][j] << " ";
}
cout << '\n';
}
cout << "二维差分数组diff(有效区域):\n";
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << diff[i][j] << " ";
}
cout << '\n';
}
return 0;
}
运行结果:
原始数组a(有效区域):
1 2 3
2 1 3
3 2 1
二维差分数组diff(有效区域):
1 1 1
1 -2 1
1 0 -3
简单应用:子矩阵更新与原数组还原
需求:给二维数组 a 中以 (x1,y1) 为左上、(x2,y2) 为右下的子矩阵所有元素增加 z,然后还原出更新后的数组 a。
核心操作(子矩阵更新):
仅需修改差分数组的 4 个端点,时间复杂度 O(1),操作如下:
diff[x1][y1] += z:子矩阵左上角开始,所有元素增加 z;diff[x1][y2+1] -= z:子矩阵右上角右侧,抵消 z(避免影响右侧区域);diff[x2+1][y1] -= z:子矩阵左下角下方,抵消 z(避免影响下方区域);diff[x2+1][y2+1] += z:子矩阵右下角的右下角,恢复 z(抵消前两步的重叠抵消,避免影响重叠区域)。
原数组还原:
更新差分数组后,对 diff 求二维前缀和,得到“更新量矩阵”,再将其与原始数组 a 相加,即可得到更新后的 a。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// 1. 初始化原始数组
int a[4][4] = {
{0, 0, 0, 0},
{0, 1, 2, 3},
{0, 2, 1, 3},
{0, 3, 2, 1}
};
// 二维差分数组(初始为0,用于记录更新操作)
int diff[4][4] = { 0 };
// 2. 输出更新前的原始数组
cout << "更新前的数组a:\n";
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << a[i][j] << " ";
}
cout << '\n';
}
// 3. 输入子矩阵更新参数:(x1,y1)~(x2,y2) 加 z
int x1, y1, x2, y2, z;
cout << "输入子矩阵坐标 (x1,y1)、(x2,y2) 及增加的z值:";
cin >> x1 >> y1 >> x2 >> y2 >> z;
// 4. 差分数组更新(修改4个端点)
diff[x1][y1] += z;
if (y2 + 1 <= 3) diff[x1][y2 + 1] -= z;
if (x2 + 1 <= 3) diff[x2 + 1][y1] -= z;
if (x2 + 1 <= 3 && y2 + 1 <= 3) diff[x2 + 1][y2 + 1] += z;
// 5. 计算差分数组的二维前缀和(得到更新量矩阵 sumdiff)
int sumdiff[4][4] = { 0 };
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
sumdiff[i][j] = sumdiff[i - 1][j] + sumdiff[i][j - 1] + diff[i][j] - sumdiff[i - 1][j - 1];
}
}
// 6. 原始数组加上更新量,得到最终数组
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
a[i][j] += sumdiff[i][j];
}
}
// 7. 输出更新后的数组
cout << "更新后的数组a:\n";
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << a[i][j] << " ";
}
cout << '\n';
}
return 0;
}
测试案例:
- 输入:
1 2 3 3 2(给子矩阵(1,2)~(3,3)加 2); - 原始子矩阵元素:2→4、3→5、1→3、3→5、2→4、1→3;
- 运行结果:
更新前的数组a:
1 2 3
2 1 3
3 2 1
更新后的数组a:
1 4 5
2 3 5
3 4 3

浙公网安备 33010602011771号