开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第25天,点击查看活动详情
差分
一维差分
差分可以看成前缀和的逆运算
B:差分数组, A:前缀和数组 A[i] = B[0] + B[1] +... + B[i] B[i] = A[i] - A[i - 1] ,每一个a[i]都是b数组中从头开始的一段区间和
- 给A数组区间[L,R]中的每个数加上常数C:B[L] += C, B[R + 1] -= C
如何构造差分数组呢,最直接的方法:
b[i] = a[i] - a[i - 1]; //构建差分数组
我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到a数组
如何得到给A数组区间[L,R]中的每个数加上常数C后的数组呢?
根据差分数组 O(N)的时间得到该数组: a[i] = b[i] + a[i - 1];
例子:
//差分 时间复杂度 o(m)
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
b[i] = a[i] - a[i - 1]; //构建差分数组
}
int l, r, c;
while (m--) //进行m次操作
{
scanf("%d%d%d", &l, &r, &c);
b[l] += c; //将序列中[l, r]之间的每个数都加上c
b[r + 1] -= c;
}
for (int i = 1; i <= n; i++)
{
//因为差分的b[i] = a[i] - a[i - 1]
//所以前缀和的a[i]: b[i] + a[i - 1]
a[i] = b[i] + a[i - 1]; //前缀和运算
printf("%d ", a[i]);
}
return 0;
}
构造差分数组的方式2:可以认为原来A数组都是全0的,但是并不是全0,然后相当于在[i,i]区间加上a[i]这个值 .... 进行n次插入操作
for (int i = 1; i <= n; i ) insert(i, i, a[i]);
- 其实是假定a数组最开始都是0,那么b数组初始时就是a数组的差分数组了
- 对于每一个a[i],相当于插入了一个数,可以直接调用insert函数即可
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c) //在[l,r]区间插入c
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
insert(i, i, a[i]);//构造差分数组
}
while (m -- ) //进行m次操作
{
int l, r, c;
cin >> l >> r >>c;
insert(l, r, c);
}
for (int i = 1; i <= n; i ++ )
{
a[i] = a[i-1]+b[i];//b[i] = a[i] - a[i-1]
printf("%d ", a[i]);
}
return 0;
}
二维差分
a[][]数组是b[][]数组的前缀和数组,那么b[][]是a[][]的差分数组,构造差分数组: b[i][j],使得a数组中a[i][j]是b数组左上角(1,1)到右下角(i,j)所包围矩形元素的和
如何构造差分数组
我们使用差分操作在对原数组进行修改的过程中,实际上就可以构造出差分数组,同一维差分,我们构造二维差分数组目的是为了 让原二维数组a中所选中子矩阵中的每一个元素加上c的操作,可以由O(n*n)的时间复杂度优化成O(1)
假设原数组a中被选中的子矩阵为 以(x1,y1)为左上角,以(x2,y2)为右下角所围成的矩形区域,想让这个区域的每个数都加上常数C
- a数组是b数组的前缀和数组,对
b数组的b[i][j]的修改,会影响到a数组中从a[i][j]及往后的每一个数
b[i][j]加C : a[i][j]及其右下角的值都加上C
void insert(int x1,int y1,int x2,int y2,int c)
{ //对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
我们可以先假想a数组为空,那么b数组一开始也为空,但是实际上a数组并不为空,因此我们每次让以(i,j)为左上角到以(i,j)为右下角面积内元素(其实就是一个小方格的面积)去插入 c = a[i][j],等价于原数组a中(i,j) 到(i,j)范围内 加上了 a[i][j] ,因此执行n*m次插入操作,就成功构建了差分b数组
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
insert(i, j, i, j, a[i][j]); //构建差分数组
}
}
关于二维差分操作也有直接的构造方法
b[i][j] = a[i][j] − a[i − 1][j] − a[i][j − 1] + a[i −1][j − 1]
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
//给(x1,y1)到(x2,y2)围成的区域每个数加上c
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
cin >> a[i][j];
insert(i, j, i, j, a[i][j]); //构建二维差分数组
}
}
while (q--) //执行q次操作
{
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
//因为b[i][j] = a[i][j] − a[i − 1][j] − a[i][j − 1] + a[i −1][j − 1]
//所以二维前缀和数组的a[i][j]:
a[i][j] = b[i][j] + a[i][j - 1] + a[i - 1][j] - a[i - 1][j - 1];
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}