1.差分的定义
对于一个给定的数组a,他的差分数组f, f [ i ] 表示 当前元素与上一个元素的差值 a [i] - a [i-1]
前缀和与差分的核⼼思想是预处理,可以在暴⼒枚举的过程中,快速给出查询的结果,从⽽优化时间 复杂度。O(1)
是经典的⽤空间替换时间的做法。
学完差分之后,⼤家会发现,前缀和与差分是⼀对互逆的运算。
2.一维差分模版
解法:
先创建差分数组,然后根据差分数组的「性质」处理 次区间修改,最后还原出来原始的数组。
1. 创建差分数组,根据定义: f [i] = a [i] − a [i − 1]
也可以根据差分数组的性质: f [i] + = a [i], f [i + 1] − = a [i]
2. 根据差分数组的性质处理 q 区间修改: f [L] + = c, f [R + 1] − = c
3.还原经过 q 次询问之后的 a 数组:对差分数组做⼀次「前缀和」,就可以还原出原数组
原出原数组
由差分数组的定义得:
f [1] = a [1]
f [2] = a [2] − a [1]
f [3] = a [3] − a [2]
......
f [i] = a [i] − a [i − 1]
原数组 a 中的每⼀项:
a [1] = f [1]
a [2] = a [2] − a [1] + a [1] = f [2] + f [1]
a [3] = a [3] − a [2] + a [2] − a [1] + a [1] = f [3] + f [2] + f [1]
......
a [i] = f [i] + f [i − 1] + f [i − 2] + ... + f [1]
模版代码:
#include<iostream>
using namespace std;
const int N = 1e5+10;
typedef long long LL;
LL n,m;
LL a[N],f[N];
int main()
{
cin>>n>>m;
for(int i = 1;i <= n;i++)
{
int x;
cin>>x;
f[i] += x;
f[i + 1] -= x;
}
LL l = 0,r = 0,k = 0;
while(m--)
{
cin>>l>>r>>k;
f[l] += k,f[r+1] -= k;
}
for(int i = 1;i <= n;i++)
{
a[i] = f[i] + a[i-1];
cout<<a[i] <<" ";
}
return 0;
}
3.一维差分例题
https://www.luogu.com.cn/problem/P3406
解法:
先考虑如何让花费最⼩,想要求最⼩花费,需要知道每⼀段⾼铁被「乘坐了多少次」,记作 f[i],那 么最⼩花费就是「买票的花费」与「买卡的花费」两者之间的最⼩值:
买票花费: a[i] × f[i] ;
买卡花费,乘⻋花费 + ⼯本费: b[i] × f[i] + c[i] ;
那么最⼩花费就是: mincost = min(a[i] × f[i], b[i] × f[i] + c[i]);
接下来考虑如何求出每⼀段⾼铁被「乘坐了多少次」。根据访问城市的序列p1,p2,p3,...pm可知,
对于任意⼀次访问 p i~p i+1,我们会乘坐[pi , p(i+1) -1]之间所有的⾼铁,⽐如 ,那么 pi = 3,
pi+1 = 6, [3,5] 之间所有的⾼铁都会被乘坐⼀次,相当于每个数都加上 1(注意 6位置不会乘坐到)
那么我们就可以利⽤「差分数组」:(差分数组就是解决子段同时加或减一个数的)
创建⼀个全为 0 的差分数组 f ;
遍历访问序列,对于每⼀次访问: f [pi] + +, f [pi+1 ] − − ;
然后对差分数组做⼀次前缀和,就得到每个⾼铁乘坐的次数
注意城市访问的序列有可能 pi > pi+1 ,此时应该「交换」⼀下顺序
参考代码:
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
LL f[N];
LL n,m;
int main()
{
cin>>n>>m;
int x;cin>>x;
for(int i = 2;i <= m;i++)
{
int y; cin>>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;
for(int i = 1;i <= n;i++)
{
LL a,b,c;
cin>>a>>b>>c;
ret += min(f[i]*a,c+f[i]*b);
}
cout<<ret<<endl;
return 0;
}
4.二维差分模版
解法:
⼆维差分模板题,先根据差分矩阵的性质创建差分矩阵,然后根据差分矩阵的性质处理q 次区间修改,最后利⽤「前缀和」还原出来原始的矩阵。因此,重点就是差分矩阵的「性质」。
可以类⽐「⼀维差分数组」的性质,推导出「⼆维差分矩阵」的性质:
在差分数组中某个位置标记:表⽰后续元素统⼀被修改;
在差分数组中求前缀和:能够还原出原始数组。
假设我们需要将原始矩阵 中,以 (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
模版:
#include<iostream>
using namespace std;
typedef long long LL;
int n,m,q;
const int N = 1010;
LL f[N][N];
//差分矩阵的性质
void insert(int x1,int y1,int x2,int y2,LL k)
{
f[x1][y1] += k;
f[x2+1][y1] -= k;
f[x1][y2+1] -= 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,x;
cin>>x1>>y1>>x2>>y2>>x;
insert(x1,y1,x2,y2,x);
}
// 利用前缀和还原修改后的数组
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;
}