1.差分定义与概念
差分和前缀和一样也是一种用空间换取时间的做法,这个算法可以让一段区间快速的加减掉一个数字,具体的做法也是预处理出一个差分数组,然后再对一段区间加减某个数的操作后还需要将这个预处理的数组还原。
1.1差分数组的创建
差分数组的创建方式有两种,假设原数组为a[N],差分数组为f[N]:
(1)利用原数组创建:
cpp
f[i] = a[i] - a[i - 1];
(2)利用差分数组的性质:
cpp
f[i] += a[i];
f[i + 1] -= a[i];
我们后面让区间加减某个数也是用的(2)的方式来执行的,因为原数组在执行任何操作前里面都是零,而我们的差分正是可以让区间加减某个数,那我们可以干脆直接用差分来创建差分数据更加的方便,因为我们不是什么时候都需要用到原数组的
1.2执行区间加减操作
我们假设区间的左端点为left, 有端点为right,我们需要在这个去区间加减的数字为k我们只需要:
cpp
f[left] += k; f[right + 1] -= k;
很显然,当left == right 时就是我们第二种创建差分数组的方式
1.3差分数据的还原
差分数组的还原方式也有两种,一种是使用原数组的方式,一种是直接使用差分数据还原的方式
使用原数组:
cpp
a[i] = a[i - 1] + f[i];
利用差分数组:
cpp
f[i] = f[i - 1] + f[i];
这个是可以用通过定义用我们高中学过推到数列公式的方法证明的,我们会用就可以了
2.差分经典例题
差分和前缀和一样都是有一维和二维的不同题型的,我个人比较喜欢直接使用差分的方式,都可以根据自己的习惯就可以了。
2.1【模板】差分
这道题是一道模板题,核心代码已经在介绍差分时讲完了,所以我就不多BB了我这里提供两种写法:
利用原数组:
cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n,m;
LL a[N];
LL f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
f[i] = a[i] - a[i - 1];
}
while (m--)
{
int l, r, k; cin >> l >> r >> k;
f[l] += k; f[r + 1] -= k;
}
for (int i = 1; i <= n; i++)
{
a[i] = a[i - 1] + f[i];
cout << a[i] << ' ';
}
return 0;
}
利用差分数组:
cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
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;
}
while (m--)
{
int 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;
}
2.2海底高铁
这道题长长的很吓人,其实算法原理很简单,因为如果我们发现这个人走过的路是确定的,所以我们可以把他走过的路视为一个区间,每当他走过这段路让这个区间加一,因为他走这段路所需的金额有两种计算方式,我们取一个最小值然后把所有段给累加起来就可以了:
cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n, m;
LL f[N];
int main()
{
cin >> n >> m;
int x; cin >> x;
for (int i = 2; i <= m; i++)
{
int y; cin >> y;
if (y > x)
{
f[x]++;
f[y]--;
}
else
{
f[y]++;
f[x]--;
}
x = y;
}
for (int i = 1; i <= n; i++)
{
f[i] = f[i - 1] + f[i];
}
LL ret = 0;
for (int i = 1; i < n; i++)
{
int a, b, c; cin >> a >> b >> c;
ret += min(a*f[i], c + b*f[i]);
}
cout << ret << endl;
return 0;
}
2.3【模板】二维差分
当我们对一段区间加上k时,如果只在左端点加了k的话当还原差分数组时会发现我们对左端点到右边的整个数组都加了k,所有我们需要对右区间-k来平衡这种影响。因此我们可以把这个性质推广到二维上。
假设我们想在一个二维平面内加减某个数(k)时,设左上角为(x1, y1) 右下角为(x2, y2)根据上面的推广就有:

可以将x为i,y为j,k为x写成代码就是:
cpp
void insert(int i1, int j1, int i2, int j2, LL x)
{
f[i1][j1] += x; f[i1][j2 + 1] -= x;
f[i2 + 1][j1] -= x; f[i2 + 1][j2 + 1] += x;
}
还原就是我们的二维前缀和:
cpp
f[i][j] = f[i][j - 1] + f[i - 1][j] - f[i - 1][j - 1] + f[i][j];
整体的代码就是:
cpp
#include <iostream>
using namespace std;
const int N = 1010;
typedef long long LL;
LL f[N][N];
int n,m,q;
void insert(int i1, int j1, int i2, int j2, LL x)
{
f[i1][j1] += x; f[i1][j2 + 1] -= x;
f[i2 + 1][j1] -= x; f[i2 + 1][j2 + 1] += x;
}
int main()
{
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
LL x; cin >> x;
insert(i, j , i , j, x);
}
}
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][j - 1] + f[i - 1][j] - f[i - 1][j - 1] + f[i][j];
cout << f[i][j] << ' ';
}
cout << endl;
}
return 0;
}
2.4地毯
这道题和模板题几乎一摸一样,所以我这里就直接贴代码了:
cpp
#include <iostream>
using namespace std;
const int N = 1010;
int f[N][N];
int n, m;
void insert(int x1, int y1, int x2, int y2)
{
f[x1][y1]++; f[x1][y2 + 1]--;
f[x2 + 1][y1]--; f[x2 + 1][y2 + 1]++;
}
int main()
{
cin >> n >> m;
while (m--)
{
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
insert(x1, y1, x2, y2);
}
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;
}
完