前缀和与差分的核⼼思想是预处理,可以在暴⼒枚举的过程中,快速给出查询的结果,从⽽优化时间复杂度。
是经典的⽤空间替换时间的做法。
学完差分之后,⼤家会发现,前缀和与差分是⼀对互逆的运算。
一、⼀维差分
题⽬来源:⽜客⽹
题⽬链接:【模板】差分
难度系数:★
1. 题目描述


2. 算法原理
解法一:暴力解法-->直接模拟 (时间复杂度过高)

解法二 :利用差分数组解决问题。
差分模板题,先「创建」差分数组,然后根据差分数组的「性质」处理 q 次区间修改,最后「还原」出来原始的数组。
- 创建差分数组,根据定义: f [i] = a[i] − a[i − 1]
也可以根据差分数组的性质: f [i] + = a[i], f [i + 1] − = a[i]

- 根据差分数组的性质处理 q 区间修改:f [L] + = c, f [R + 1] − = c

- 还原经过 q 次询问之后的 a 数组:对差分数组做⼀次「前缀和」,就可以还原出原数组
由差分数组的定义得 : 原数组 a 中的每⼀项:




注意:差分适用于只最后一次查询的题目,如果遇到边查询边输出的题目使用线段树。
3. 参考代码
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, m;
LL f[N]; // 差分数组
int main()
{
cin >> n >> m;
// 利用差分数组的性质,创建差分数组
for(int i = 1; i <= n; i++)
{
LL x; cin >> x;
f[i] += x;
f[i + 1] -= x;
}
// 处理 m 次修改操作
while(m--)
{
LL l, r, k; cin >> l >> r >> k;
f[l] += k; f[r + 1] -= k;
}
// 还原出原始的数组
for(int i = 1; i <= n; i++)
{
f[i] = f[i - 1] + f[i];
cout << f[i] << " ";
}
return 0;
}
二、海底⾼铁
题⽬来源:洛⾕
题⽬链接:P3406 海底高铁 - 洛谷
难度系数:★★
1. 题目描述



2. 算法原理
先考虑如何让花费最⼩,想要求最⼩花费,需要知道每⼀段⾼铁被「乘坐了多少次」,记作么最⼩花费就是「买票的花费」与「买卡的花费」两者之间的最⼩值:
- 买票花费: a[i] × f [i] ;f [i] ,那
- 买卡花费,乘⻋花费 + ⼯本费:b[i] × f [i] + c[i] ;
- 那么最⼩花费就是: min cost = min(a[i] × f [i], b[i] × f [i] + c[i])
接下来考虑如何求出每⼀段⾼铁被「乘坐了多少次」。
根据访问城市的序列p1 , p2 , p3 , ..., pm可知
对于任意⼀次访问pi ∼ pi+1 ,我们会乘坐[pi, pi+1 − 1]之间所有的⾼铁,⽐如pi = 3, pi+1 = 6,那么[3, 5]之间所有的⾼铁都会被乘坐⼀次,相当于每个数都加上1 ,「注意 6位置不会乘坐到」。那么我们就可以利⽤「差分数组」:
- 创建⼀个全为 0 的差分数组 f ;
- 遍历访问序列,对于每⼀次访问: f [pi] + +, f [pi+1 ] − − ;
- 然后对差分数组做⼀次前缀和,就得到每个⾼铁乘坐的次数
注意城市访问的序列有可能pi > pi+1 ,此时应该「交换」⼀下顺序。

3. 参考代码
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, m;
LL f[N]; // 差分数组
int main()
{
cin >> n >> m;
// x->y
int x; cin >> x;
for(int i = 2; i <= m; i++)
{
int y; cin >> y;
// x -> y
if(x > y)
{
f[y]++;
f[x]--;
}
else
{
f[x]++;
f[y]--;
}
x = y;
}
// 利用差分数组,还原出原数组
for(int i = 1; i <= n; i++) f[i] += f[i - 1];
// 直接求结果
LL ret = 0;
for(int i = 1; i < n; i++)
{
LL a, b, c; cin >> a >> b >> c;
ret += min(a * f[i], c + b * f[i]);
}
cout << ret << endl;
return 0;
}
三、⼆维差分
题⽬来源:⽜客⽹
题⽬链接:【模板】二维差分
难度系数:★
1. 题目描述


2. 算法原理
⼆维差分模板题,先根据差分矩阵的「性质」创建差分矩阵,然后根据差分矩阵的「性质」处理 q 次区间修改,最后利⽤「前缀和」还原出来原始的矩阵。因此,重点就是差分矩阵的「性质」。
可以类⽐「⼀维差分数组」的性质,推导出「⼆维差分矩阵」的性质:
- 在差分数组中某个位置标记:表⽰后续元素统⼀被修改;
- 在差分数组中求前缀和:能够还原出原始数组。
假设我们需要将原始矩阵 a 中,以 (x1 , y1 ) 为左上⻆, (x2 , y2 ) 为右下⻆的⼦矩阵的每个元素都加上 k :

由此可得差分矩阵的性质:
f [x1 ][y1 ]+ = k
f [x1 ][y2 + 1]− = k
f [x2 + 1][y1 ]− = k
f [x2 + 1][y2 + 1]+ = k

3. 参考代码
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1010;
int n, m, q;
LL f[N][N]; // 差分矩阵
// 差分矩阵的性质
void insert(int x1, int y1, int x2, int y2, LL k)
{
f[x1][y1] += k; f[x1][y2 + 1] -= k; f[x2 + 1][y1] -= k; f[x2 + 1][y2 + 1] += k;
}
int main()
{
cin >> n >> m >> q;
// 预处理差分矩阵
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
LL x; cin >> x;
// [i, j]为左上角,[i, j]为右下角的矩阵,统一加上 x
insert(i, j, i, j, x);
}
}
// 处理 q 次修改操作
while(q--)
{
LL x1, y1, x2, y2, k; cin >> x1 >> y1 >> x2 >> y2 >> k;
insert(x1, y1, x2, y2, k);
}
// 利用前缀和还原出修改之后的数组
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + f[i][j];
cout << f[i][j] << " ";
}
cout << endl;
}
return 0;
}
四、地毯
题⽬来源:洛⾕
题⽬链接:P3397 地毯 - 洛谷
难度系数:★
1. 题目描述


2. 算法原理
直接利⽤⼆维差分矩阵即可。
3. 参考代码
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N]; // 差分矩阵
// 差分数组的性质
void insert(int x1, int y1, int x2, int y2, int k)
{
f[x1][y1] += k; f[x1][y2 + 1] -= k; f[x2 + 1][y1] -= k; f[x2 + 1][y2 + 1] += k;
}
int main()
{
cin >> n >> m;
// 构建差分数组
while(m--)
{
int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
insert(x1, y1, x2, y2, 1);
}
// 利用前缀和还原修改之后的数组
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + f[i][j];
cout << f[i][j] << " ";
}
cout << endl;
}
return 0;
}
1309

被折叠的 条评论
为什么被折叠?



