一、前缀和与差分基础概念
1. 前缀和的定义与特点
前缀和思想:通过预处理构建一个前缀和数组/矩阵,用于快速计算区间和/子矩阵和
适用场景:频繁查询数组或矩阵的区间和/子矩阵和
核心特点:预处理时间复杂度 O(n)或 O(n2),查询时间复杂度 O(1)
2. 差分的定义与特点
差分思想:通过构建差分数组/矩阵,实现对原数组的高效区间/子矩阵修改
适用场景:需要对数组进行频繁的区间增减操作
核心特点:区间更新时间复杂度 O(1),还原数组时间复杂度 O(n)或 O(n2)
3. 状态关系公式(通常:a为原数组,s前缀和数组,d差分数组)
一维前缀和:s[ ]
s[i]:表示原数组a中前i个元素的和(包含第i个元素)
即将:s[i] = a[1] + a[2] + ... + a[i]
构建前缀和数组:s[i]=s[i−1] + a[i]
区间查询:sum[L,R] = s[R]−s[L−1]
一维差分: d[ ]
d[i]:表示原数组a中第i个元素与第i-1个元素的差值
构建差分数组:d[i] = a[i] − a[i−1]
区间更新([L,R]增加c):
d[L] += c
d[R+1] −= c
还原数组:a[i] = a[i−1] + d[i]
二维前缀和::s[ ][ ]
s[i][j]:表示从原矩阵左上角(1,1)到右下角(i,j)形成的子矩阵中所有元素的和
以(1,1)和(i,j)为对角线的矩形区域总和
构建前缀和矩阵:s[i][j] = s[i−1][j] +s[i][j−1] − s[i−1][j−1] + a[i][j]
子矩阵查询:sum(x1,y1,x2,y2) = s[x2][y2] − s[x1−1][y2] − s[x2][y1−1] + s[x1−1][y1−1]
二维差分:d[ ][ ]
d[i][j]:表示原矩阵a中元素a[i][j]与其相邻元素的复合差值关系
构建差分矩阵:d[i][j] = a[i][j] − a[i−1][j] − a[i][j−1] + a[i−1][j−1]
子矩阵更新((x1,y1)到(x2,y2)增加c):
d[x1][y1] += c d[x2+1][y1] -= c d[x1][y2+1] -= c d[x2+1][y2+1] += c
还原数组:a[i][j] = a[i−1][j] + a[i][j−1] − a[i−1][j−1] + d[i][j]
状态关系总结表
二、应用演示案例
演示1:一维前缀和(前n项和)
问题描述:
小明每天记录自己的学习时长,现在他想知道前n天的总学习时长。给定一个包含每天学习时长的数组a,请帮他快速回答多次查询。
输入格式:
第一行两个整数n和q,表示天数和查询次数
第二行n个整数,表示每天的学习时长
接下来q行,每行一个整数k,表示查询前k天的总时长
输出格式:
对于每个查询,输出一行结果
输入样例:
5 3
1 2 3 4 5
1
3
5
输出样例:
1
6
15
解法代码:
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int N = 1e5+5;
4 int s[N],a[N];
5
6 int main() {
7 int n, q;
8 cin >> n >> q;
9 for(int i=1; i<=n; i++) {
10 cin >> a[i];
11 s[i] = s[i-1] + a[i]; // 构建前缀和
12 }
13 while(q--) {
14 int k;
15 cin >> k;
16 cout << s[k] << endl;
17 }
18 return 0;
19 }
演示2:一维前缀和(区间和查询)
问题描述:
某商店有n件商品,每件商品有一个价格。现在要进行促销活动,需要频繁计算某段区间内商品的总价格。请设计一个高效的系统来处理这些查询。
输入格式:
第一行两个整数n和q,表示商品数量和查询次数
第二行n个整数,表示每件商品的价格
接下来q行,每行两个整数L和R,表示查询区间
输出格式:
对于每个查询,输出一行结果
输入样例:
5 3
10 20 30 40 50
1 3
2 5
3 3
输出样例:
60
140
30
解法代码:
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int N = 1e5+5;
4 int s[N],a[N];
5
6 int main() {
7 int n, q;
8 cin >> n >> q;
9 for(int i=1; i<=n; i++) {
10 cin >> a[i];
11 s[i] = s[i-1] + a[i]; // 构建前缀和
12 }
13 while(q--) {
14 int L, R;
15 cin >> L >> R;
16 cout << s[R] - s[L-1] << endl; // 区间和查询
17 }
18 return 0;
19 }
演示3:长度为k的区间和(滑动窗口前缀和)
问题描述:
某气象站连续记录了n天的降水量数据。现在需要找出所有长度为k的连续天数区间中,降水量总和最大的那个区间。请设计一个高效算法解决这个问题。
输入格式:
第一行两个整数n和k,表示天数和要求区间长度
第二行n个整数,表示每天的降水量
输出格式:
一个整数,表示长度为k的区间中最大的降水量总和
输入样例1:
6 3
1 4 2 10 3 5
输出样例1:
18
解释:区间[4,6]的和为10+3+5=18
输入样例2:
5 2
5 2 1 8 3
输出样例2:
11
解释:区间[4,5]的和8+3=11
解法分析
前缀和预处理:先计算前缀和数组s
滑动窗口计算:所有长度为k的区间和可以表示为s[i] - s[i-k](i从k到n)
维护最大值:遍历过程中维护最大的区间和
解法代码
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int N = 1e5+5;
4 int s[N],a[N];
5
6 int main() {
7 int n, k;
8 cin >> n >> k;
9 for(int i=1; i<=n; i++) {
10 cin >> a[i];
11 s[i] = s[i-1] + a[i]; // 构建前缀和
12 }
13
14 int max_sum = INT_MIN;
15 for(int i=k; i<=n; i++) {
16 int current = s[i] - s[i-k]; // 计算长度为k的区间和
17 max_sum = max(max_sum, current);
18 }
19
20 cout << max_sum << endl;
21 return 0;
22 }
演示4:一维差分(区间批量更新)
问题描述:
某游戏有n个关卡,每个关卡有一个初始难度。现在要进行m次调整,每次将第L到第R关的难度增加c。请输出最终每个关卡的难度。
输入格式:
第一行两个整数n和m
第二行n个整数,表示各关卡的初始难度
接下来m行,每行三个整数L, R, c
输出格式:
一行n个整数,表示最终每个关卡的难度
输入样例:
5 3
3 1 4 2 5
1 3 2
2 5 1
3 4 -1
输出样例:
5 4 6 2 6
解法分析
差分数组构建:根据初始数组构建差分数组d,其中d[i] = a[i] - a[i-1](a[0]=0)
区间更新:对差分数组进行区间更新操作
还原数组:通过差分数组还原最终结果数组
解法代码:
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int N = 1e5+5;
4 int a[N], d[N];
5
6 int main() {
7 int n, m;
8 cin >> n >> m;
9
10 // 输入初始难度并构建差分数组
11 for(int i=1; i<=n; i++) {
12 cin >> a[i];
13 d[i] = a[i] - a[i-1];
14 }
15
16 // 执行m次区间更新
17 while(m--) {
18 int L, R, c;
19 cin >> L >> R >> c;
20 d[L] += c;
21 d[R+1] -= c;
22 }
23
24 // 还原最终难度数组
25 for(int i=1; i<=n; i++) {
26 a[i] = a[i-1] + d[i];
27 cout << a[i] << " ";
28 }
29
30 return 0;
31 }
演示5:二维前缀和(子矩阵和查询)
问题描述:
某农场被划分成n×n的网格,每个网格有一定数量的苹果。现在要统计多个矩形区域内的苹果总数。
输入格式:
第一行一个整数n
接下来n行,每行n个整数表示每个网格的苹果数
接下来一行一个整数q
接下来q行,每行四个整数x1,y1,x2,y2表示查询的矩形区域
输出格式:
对于每个查询,输出一行结果
输入样例:
3
1 2 3
4 5 6
7 8 9
2
1 1 2 2
2 2 3 3
输出样例:
12
28
解法代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int s[N][N],a[N][N];
int main() {
int n;
cin >> n;
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
cin >> a[i][j];
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]; // 构建二维前缀和
}
}
int q;
cin >> q;
while(q--) {
int x1,y1,x2,y2;
cin >> x1 >> y1 >> x2 >> y2;
int res = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]; // 子矩阵查询
cout << res << endl;
}
return 0;
}
三、如何识别前缀和/差分问题
1. 前缀和问题的识别特征
关键词:频繁查询区间和、子矩阵和、静态数据(数据不修改)
场景特征:
需要多次计算数组某段区间的累加和
需要计算矩阵中某个子矩阵的元素和
题目强调查询次数多,要求查询时间尽可能快
2. 差分问题的识别特征
关键词:批量区间修改、多次更新后查询、离线处理
场景特征:
需要对数组的某个区间进行统一增减操作
需要先进行多次修改,最后才查询结果
题目强调修改次数多,要求修改时间尽可能快
3. 综合判断技巧
如果题目既有区间修改又有区间查询,可能需要结合线段树或树状数组
前缀和通常用于"静态数据+频繁查询"场景
差分通常用于"频繁修改+最后查询"场景
当数据维度从一维变为二维时,考虑二维前缀和/差分
典型例题模式:
"给定一个数组,进行q次查询,每次查询区间[L,R]的和" → 前缀和
"给定一个数组,进行q次操作,每次将[L,R]区间内的数加c,最后输出数组" → 差分
"给定一个矩阵,查询多个子矩阵的和" → 二维前缀和
"给定一个矩阵,对多个子矩阵进行增减操作,最后输出矩阵" → 二维差分