参考博客
一维差分 二维差分
一维差分
一维差分在前面讲解的树状数组 的区间修改与查询中有具体应用
定义
差分是一种前缀和相对的策略。对于一个数列 ai ,我们维护的数据是“相邻两个数的差”
我们称数列 pi 就是数列 ai 的差分数列
应用
维护序列并查询某个数
譬如使 [l,r]
上的每个数加上 k ,那么维护序列 p 修改操作是:
查询 a[i] 的操作就是做一遍前缀和:
维护序列并查询某个区间的和
维护序列的操作和上述相同,借助上述求某个值的方法可以发现如下规律:
所以我们可以在每次维护 p[i] 的同时,在维护一个序列 q[i]=(i-1)*p[i]
,如此查询区间和的时候只用将 p[] 的前缀和减去 q[] 的前缀和即可:
模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <bits/stdc++.h> using namespace std ;int n, m;int a[100005 ], p[100005 ], q[100005 ];void input (int i, int x) { a[i] = x; p[i] = a[i] - a[i - 1 ]; q[i] = (i - 1 ) * p[i]; } void change (int l, int r, int c) { p[l] += c, p[r + 1 ] -= c; q[l] += (l - 1 ) * c, q[r + 1 ] -= r * c; } int query (int pos) { int ans = 0 ; for (int i = 1 ; i <= pos; ++i){ ans += p[i]; } return ans; } int getsum (int pos) { int ans = 0 ; for (int i = 1 ; i <= pos; ++i){ ans += pos*p[i] - q[i]; } return ans; } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); while (cin >> n >> m){ for (int i = 1 ; i <= n; ++i){ int x; cin >> x; input(i, x); } while (m--){ int l, r, c; cin >> l >> r >> c; change(l, r, c); } for (int i = 1 ; i <= n; ++i){ cout << query(i) << ' ' << getsum(i) << endl ; } cout << endl ; } return 0 ; }
二维差分
定义
二维差分就是一维差分的拓展,其原理没有任何变化
根据二维前缀和表示的是右上角矩形的和,由于差分只涉及前面相邻的数(由一维可以推出),并且由前面范围的数相加得到这个位置的数。那么类比二维前缀和和一维差分,可以简单推测出二维差分的公式:
即当前点的差分 p[i][j] 是 a[i][j] 的与左边和上边相邻点的差值、与左上角相邻点的加和的总和
应用
由上述定义逆推就可以得到查询某个点的方法:
二维差分的维护涉及重叠问题,如果我们要在左上角是 (x1,y1),右下角是 (x2,y2) 的矩形区间每个值都 +a,如下图所示
在我们要的区间开始位置(x1,y1)处 +c,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分 -c 消除 +c 的影响,而两个蓝色部分重叠的绿色部分多了个 -c 的影响,所以绿色部分 +c 消除影响。所以对应的计算方法如下:
1 2 3 4 5 6 void change (int x1, int y1, int c, int x2, int y2) { p[x1][y1] += c; p[x1][y2+1 ] -=c; p[x2+1 ][y1] -=c; p[x2+1 ][y2+1 ] += c; }
如果将某个 a*b 的矩阵以左上角 (i,j)
为出发点的所有点加上 c 就可以表示为:
1 2 3 4 5 6 7 void change (int x, int y, int k, int a, int b) { int dx = x + a, dy = y + b; p[x][y] += k; p[dx][y] -= k; p[x][dy] -= k; p[dx][dy] += k; }
模板
模板题1:输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1, y1) 和 (x2, y2) 表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上 c。请你将进行完所有操作后的矩阵输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> using namespace std ;const int maxn = 1e3 +5 ;int a[maxn][maxn];int p[maxn][maxn];void change (int x1, int y1, int c, int x2, int y2) { p[x1][y1] += c; p[x1][y2+1 ] -=c; p[x2+1 ][y1] -=c; p[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]); p[i][j] = a[i][j]-a[i-1 ][j]-a[i][j-1 ]+a[i-1 ][j-1 ]; } } for (int i=0 ; i<q; i++) { int x1, y1, x2, y2, c; scanf ("%d%d%d%d%d" , &x1, &y1, &x2, &y2, &c); change(x1, y1, c, x2, y2); } for (int i=1 ; i<=n; i++) { for (int j=1 ; j<=m; j++) { p[i][j] += p[i-1 ][j]+p[i][j-1 ]-p[i-1 ][j-1 ]; printf ("%d " , p[i][j]); } printf ("\n" ); } return 0 ; }
模板题2:2020ICPC小米邀请赛网络赛第一场J题