前缀和与差分算法参考资料
一、前缀和与差分基础概念
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] 
状态关系总结表
| 状态类型 | 表示符号 | 状态定义 | 核心作用 | 
|---|---|---|---|
| 一维前缀和 | s[i] | 前i项累加和 | 快速区间查询 | 
| 一维差分 | d[i] | 相邻元素差值 | 高效区间修改 | 
| 二维前缀和 | s[i][j] | 子矩阵累加和 | 快速子矩阵查询 | 
| 二维差分 | 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:
输出样例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,最后输出数组" → 差分 
- 
"给定一个矩阵,查询多个子矩阵的和" → 二维前缀和 
- 
"给定一个矩阵,对多个子矩阵进行增减操作,最后输出矩阵" → 二维差分 
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号