差分详解(里面包括二维差分)
差分的定义
给你一组数据 a 1 a 2 a 3 … … a n a1 a2 a3 …… an a1a2a3……an,你需要构造一个数组使得 b 1 b 2 b 3 … … b n b1 b2 b3 …… bn b1b2b3……bn,使得 b b b数组的前缀和是 a a a.(注意:b数组是自己需要构造的)
b 1 = a 1 b1=a1 b1=a1
b 2 = a 2 − a 1 b2=a2-a1 b2=a2−a1
… … …
b n = a n − a n − 1 bn=an-an-1 bn=an−an−1
a数组就称为b的前缀和,b数组就称为a的差分。
何时可以用到差分
我们有了b数组,如何来求出原数组呢?
就是对b数组求一遍前缀和。
给你一组数字 a 1 a 2 a 3 … … a n a1 a2 a3 …… an a1a2a3……an,现在要对这一组数字里面的一些数字中从 a l al al到 a r ar ar中的每一个数都加上 c c c,然后要你求修改后区间 [ l , r ] [l,r] [l,r]以内的和。
1:构造b数组
2:将 b r br br加上 c c c ,联想a数组的含义, a数组就是b数组的前缀和,所以 a r + c ar+c ar+c, a r + 1 + c ar+1+c ar+1+c,……, a n + c an+c an+c
3:但是我们需要保证 [ r + 1 , n ] [r+1,n] [r+1,n]里面的数字不能加上c,所以将 b r + 1 − c br+1-c br+1−c, 就可以保证 a r + 1 − c ar+1-c ar+1−c, a r + 2 − c ar+2-c ar+2−c,…… a n − c an-c an−c.
所以我们插入数组的主要步骤就是
{
b
l
+
c
b
r
+
1
−
c
\begin{cases} bl+c\\ br+1-c \end{cases}
{bl+cbr+1−c
本来修改
[
l
,
r
]
[l,r]
[l,r]里面的数需要
o
(
n
)
o(n)
o(n)的时间复杂度,现在只需要修改两个数字的值,复杂度变成了
o
(
1
)
o(1)
o(1)
如何快速构造出b数组来
1:我们想假象一下a数组里面的数全部是0,由定义差分数组b也全部是0;
0 0 0 0 0 0
a 1 a1 a1 a 2 a2 a2 a 3 a3 a3 a 4 a4 a4 …… a n − 1 an-1 an−1 a n an an
2:但是这只是我们的假象,实际的a数组不是0,所以我们需要进行n次插入操作。
3:下面进行n次插入操作
[ 1 , 1 ] + a 1 [1,1]+a1 [1,1]+a1
[ 2 , 2 ] + a 2 [2,2]+a2 [2,2]+a2
[ 3 , 3 ] + a 3 [3,3]+a3 [3,3]+a3
………………
[ a n , a n ] + a n [an,an]+an [an,an]+an
4:经过n次插入操作之后就将b数组构造出来了(根据前面插入操作的定义,插入操作是将 [ l , r ] [l,r] [l,r]区间内的每一个数都加上 c c c)
综上所述:差分总共就一个操作,就是插入操作
例子
题意:现在给你一个长度为n整数序列,现在有m个操作,我们需要将 [ l , r ] [l,r] [l,r]中的每一个数都加上 c c c,请你输出进行完操作后的所有序列。
输入:第一行为n和m;
第二行为n个整数,表示整数序列;
接下来m行每一行有三个数,分别是l,r,c.
(其中1<=n,m<=100000 1<=l<r<=n -1000<=c<=1000 -1000<=序列中元素的值<=1000)
ac代码
#include<iostream>
using namespace std;
const int N = 100010;
int a[N], b[N];
void insert(int l, int r, int c) {
b[l] += c;
b[r + 1] -= c;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)scanf("%d", &a[i]);
for (int i = 1; i <= n; i++)insert(i, i, a[i]);
while (m--) {
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
for (int i = 1; i <= n; i++) {
b[i] += b[i - 1];
printf("%d ", b[i]);
}
return 0;
}
二维差分详解
有了一维差分的基础后,我们对于二维差分就很轻松了,一维差分处理的是一个区间,二维差分处理的是一个子矩阵,我们每次对子矩阵里面的每一个数都加上c,最后要我们求出修改后的矩阵里面的每一个数。
1:构造 b [ i ] [ j ] b[i][j] b[i][j]数组
b [ i ] [ j ] b[i][j] b[i][j]数组的定义: a [ i ] [ j ] a[i][j] a[i][j]数组是 b [ i ] [ j ] b[i][j] b[i][j]数组的前缀和,这个数组是我们假象出来的然后满足某种性质。
我们在1*1的子矩阵内插入一个数 a [ i ] [ j ] a[i][j] a[i][j]
2:插入操作的求法
其中
b
[
x
+
1
]
[
y
+
1
]
b[x+1][y+1]
b[x+1][y+1]就是绿色部分的面积和红色部分的面积。
题目:
ac代码
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
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;
scanf("%d%d%d", &n, &m,&q);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
insert(i, j, i, j, a[i][j]);
while (q--) {
int x1, y1, x2, y2,c;
scanf_s("%d%d%d%d%d", &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] += (b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1]);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
printf("%d ", b[i][j]);
printf("\n");
}
}