前缀和与差分
概念讲解
前缀和
前缀和是指数组前 i 个元素的和。
一维前缀和:
区间和查询:
差分
差分是前缀和的逆运算。
差分数组:
区间修改: 将 [l, r] 区间的每个元素加上 c
一维前缀和
构建
12345678int a[N], prefix[N];
int n;
// 构建前缀和
prefix[0] = a[0];
for (int i = 1; i < n; i++) {
prefix[i] = prefix[i - 1] + a[i];
}查询
12345// 查询区间 [l, r] 的和
int query(int l, int r) {
if (l == 0) return prefix[r];
return prefix[r] - prefix[l - 1];
}二维前缀和
构建
12345678910int a[N][N], prefix[N][N];
int n, m;
// 构建二维前缀和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
prefix[i][j] = prefix[i-1][j] + prefix[i][j-1]
- prefix[i-1][j-1] + a[i][j];
}
}查询
12345// 查询子矩阵 [(x1,y1), (x2,y2)] 的和
int query(int x1, int y1, int x2, int y2) {
return prefix[x2][y2] - prefix[x1-1][y2]
- prefix[x2][y1-1] + prefix[x1-1][y1-1];
}差分数组
构建
12345678int a[N], diff[N];
int n;
// 构建差分数组
diff[0] = a[0];
for (int i = 1; i < n; i++) {
diff[i] = a[i] - a[i - 1];
}区间修改
12345// 将 [l, r] 区间的每个元素加上 c
void update(int l, int r, int c) {
diff[l] += c;
diff[r + 1] -= c;
}还原数组
1234// 还原数组
for (int i = 1; i < n; i++) {
diff[i] += diff[i - 1];
}二维差分
区间修改
1234567// 将子矩阵 [(x1,y1), (x2,y2)] 的每个元素加上 c
void update(int x1, int y1, int x2, int y2, int c) {
diff[x1][y1] += c;
diff[x2 + 1][y1] -= c;
diff[x1][y2 + 1] -= c;
diff[x2 + 1][y2 + 1] += c;
}还原数组
123456// 还原数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
}
}考点分析
初赛考查方式
- 选择题: 前缀和的计算
- 程序阅读: 理解前缀和的构建过程
- 编程题: 前缀和应用
常见题型
- 区间和查询
- 区间修改
- 二维矩阵问题
例题精讲
例题 1:区间和查询
题目: 给定数组,多次查询区间 [l, r] 的和。
解答:
1234567891011121314151617181920212223#include <iostream>
using namespace std;
int main() {
int n, q;
cin >> n >> q;
long long a[n + 1], prefix[n + 1];
prefix[0] = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
prefix[i] = prefix[i - 1] + a[i];
}
while (q--) {
int l, r;
cin >> l >> r;
cout << prefix[r] - prefix[l - 1] << endl;
}
return 0;
}例题 2:差分应用
题目: 将数组的 [l, r] 区间每个元素加上 c。
解答:
12345678910111213141516171819202122232425262728293031#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
long long a[n + 1], diff[n + 2];
fill(diff, diff + n + 2, 0);
for (int i = 1; i <= n; i++) {
cin >> a[i];
diff[i] = a[i] - a[i - 1];
}
while (m--) {
int l, r, c;
cin >> l >> r >> c;
diff[l] += c;
diff[r + 1] -= c;
}
// 还原数组
for (int i = 1; i <= n; i++) {
diff[i] += diff[i - 1];
cout << diff[i] << " ";
}
cout << endl;
return 0;
}练习题
- 洛谷 P1115 最大子段和
查看答案
思路:使用前缀和,维护前缀和的最小值,用当前前缀和减去最小前缀和得到最大子段和。
12345678910111213141516171819#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
long long prefix = 0, minPrefix = 0, ans = -1e18;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
prefix += x;
ans = max(ans, prefix - minPrefix);
minPrefix = min(minPrefix, prefix);
}
cout << ans << endl;
return 0;
}- 洛谷 P3397 地毯
查看答案
思路:使用二维差分,将矩形区域的修改转化为四个角的修改,最后还原数组。
1234567891011121314151617181920212223242526272829#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
int diff[n + 2][n + 2] = {0};
while (m--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
diff[x1][y1]++;
diff[x2 + 1][y1]--;
diff[x1][y2 + 1]--;
diff[x2 + 1][y2 + 1]++;
}
// 还原数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];
cout << diff[i][j] << " ";
}
cout << endl;
}
return 0;
}- 洛谷 P2004 旅行投资
查看答案
思路:使用二维前缀和,计算每个子矩阵的和,找出最大的子矩阵和。
1234567891011121314151617181920212223242526272829303132333435#include <iostream>
using namespace std;
int main() {
int n, m, c;
cin >> n >> m >> c;
long long a[n + 1][m + 1], prefix[n + 1][m + 1] = {0};
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1]
- prefix[i - 1][j - 1] + a[i][j];
}
}
long long ans = -1e18;
int x = 1, y = 1;
for (int i = c; i <= n; i++) {
for (int j = c; j <= m; j++) {
long long sum = prefix[i][j] - prefix[i - c][j]
- prefix[i][j - c] + prefix[i - c][j - c];
if (sum > ans) {
ans = sum;
x = i - c + 1;
y = j - c + 1;
}
}
}
cout << x << endl << y << endl;
return 0;
}易错点
⚠️ 注意:
- 前缀和数组下标从 1 开始可以简化计算
- 二维前缀和要注意容斥原理
- 差分还原后才是原数组
- 注意边界条件