二维差分(讲解)
转载于:https://blog.csdn.net/justidle/article/details/104506724
什么是二维差分
我们有一个矩阵,如下图所示。
根据二维前缀和表示的是右上角矩形的和,由于差分只涉及前面相邻的数(由一维可以推出),并且由前面范围的数相加得到这个位置的数。那么类比二维前缀和和一维差分,可以简单推测出二维差分的公式
如何从差分矩阵得到原矩阵呢?可以参考下面公式
举例
比如,我们有一个矩阵 a,如下所示
1 2 4 3
5 1 2 4
6 3 5 9
那么对应的二维差分矩阵 p 如下:
1 1 2 -1
4 -5 -1 3
1 1 1 2
应用
如果我们要在左上角是 (x1,y1),右下角是 (x2,y2) 的矩形区间每个值都 +a,如下图所示
在我们要的区间开始位置(x1,y1)处 +c,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分 -c 消除 +c 的影响,而两个蓝色部分重叠的绿色部分多了个 -c 的影响,所以绿色部分 +c 消除影响。所以对应的计算方法如下:
diff[x1][y1] += c; diff[x1][y2+1] -=c; diff[x2+1][y1] -=c; diff[x2+1][y2+1] += c;
模板题:https://ac.nowcoder.com/acm/contest/7501/J
题意
给你一个 n,m 的矩阵。
你每次可以选择一个a,b 的矩阵,每个元素都同时减去1。
问你经过一些操作之后,能否得到全0的矩阵。
数据范围
样例组数 T ≤ 100
n , m ≤ 1000
a , b ≤ 1000
代码:
///难理解版本: const int MAX = 1e3+50; ll aa[MAX][MAX]; ll de[MAX][MAX]; void ins(int x,int y,ll c,int a,int b){ int dx = x + a; int dy = y + b; de[x][y] -= c; de[x][dy] += c; de[dx][y] += c; de[dx][dy] -= c; } int main() { int T; scanf("%d",&T); while(T--){ int n,m,a,b; scanf("%d%d%d%d",&n,&m,&a,&b); for(int i = 0;i <= n+1;++i) for(int j = 0;j <= m+1;++j) de[i][j] = 0; for(int i = 1;i <= n;++i) for(int j = 1;j <= m;++j){ scanf("%d",&aa[i][j]); ins(i,j,-aa[i][j],1,1); /// 负号意味着加上 } bool flag = true; for(int i = 1;flag && i <= n;++i){ for(int j = 1;flag && j <= m;++j){ de[i][j] = de[i][j] + de[i][j-1] + de[i-1][j] - de[i-1][j-1]; if(de[i][j] == 0)continue; if(de[i][j] < 0)flag = false; if(de[i][j] > 0){ if(i + a - 1 > n || j + b - 1 > m)flag = false; else{ ins(i,j,de[i][j],a,b); /// 正号意味着减去 } } } } if(flag)puts("^_^"); else puts("QAQ"); } return 0; }
#include<bits/stdc++.h> #include<cstdio> #include<algorithm> #define ll long long using namespace std; const int MMAX=1e3+5; int mp[MMAX][MMAX],re[MMAX][MMAX]; int n,m,a,b,t,res[MMAX][MMAX]; /// mp[][]存初始矩阵,re[][]存前缀差分矩阵,res[][]存最后遍历得到的终极差分矩阵 int main() { scanf("%d",&t); while(t--) { scanf("%d%d%d%d",&n,&m,&a,&b); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%d",&mp[i][j]); re[i][j]=mp[i][j]-mp[i-1][j]-mp[i][j-1]+mp[i-1][j-1]; ///初始化前缀差分矩阵re[][] } } int f=0; ///定义标记变量,为0代表可以,为1代表不可以 for(int i=1;i<=n-a+1;i++){ for(int j=1;j<=m-b+1;j++){ res[i][j]=re[i][j]+res[i-1][j]+res[i][j-1]-res[i-1][j-1]; ///计算更新过的ij位置的值 if(res[i][j]<0) f=1; ///如果小于0代表不符合要求 if(f==1) break; if(res[i][j]==0) continue; ///如果大于0代表需要减1操作res[i][j]次 re[i][j]-=res[i][j]; re[i+a][j]+=res[i][j]; re[i][j+b]+=res[i][j]; re[i+a][j+b]-=res[i][j]; ///上述四步是核心代码,即更新区间[i][j]到[i+a][j+b]区间的值 res[i][j]=0; } if(f==1) break; } if(f==1) printf("QAQ\n"); ///如果不符合直接输出QAQ else{ for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ ///遍历寻找是否全部满足 res[i][j]=re[i][j]+res[i-1][j]+res[i][j-1]-res[i-1][j-1]; if(res[i][j]!=0) f=1; if(f==1) break; } if(f==1) break; } if(f==1) printf("QAQ\n"); else printf("^_^\n"); } } return 0; }