前言
有一维的前缀和以及差分当然有二维的~
一、二维前缀和
1.内容
二维前缀和就是求二维数组上从(0,0)位置到(i,j)位置的累加和。
2.模板——二维区域和检索 - 矩阵不可变
class NumMatrix {
public:vector<vector<int>>sum;NumMatrix(vector<vector<int>>& matrix) {int n=matrix.size();int m=matrix[0].size();sum.resize(n+2,vector<int>(m+2,0));for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){sum[i][j]=matrix[i-1][j-1];}}//二位前缀和for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){sum[i][j]+=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1];}}}int sumRegion(int row1, int col1, int row2, int col2) {row1++;row2++;col1++;col2++;return sum[row2][col2]-sum[row2][col1-1]-sum[row1-1][col2]+sum[row1-1][col1-1];}
};
二维前缀和的求法就是,自己+左+上-左上。
此外,若想求点(a,b)到(c,d)区间上的累加和,求法就是在前缀和数组中,求(c,d)-(c,b-1)-(a-1,b)+(a-1,b-1)即可,具体原理就是一个容斥原理,大的矩形减去左边和上边两个小矩形,再加上左上角多减去一次的小矩形。
3.最大的以 1 为边界的正方形
class Solution {
public:int largest1BorderedSquare(vector<vector<int>>& grid) {int n=grid.size();int m=grid[0].size();build(n,m,grid);if(sum(0,0,n-1,m-1,grid)==0){return 0;}int ans=1;for(int a=0;a<n;a++){for(int b=0;b<m;b++){for(int c=a+ans,d=b+ans,k=ans+1;c<n&&d<m;c++,d++,k++){if(sum(a,b,c,d,grid)-sum(a+1,b+1,c-1,d-1,grid)==(k-1)<<2){ans=k;}}}}return ans*ans;}void build(int n,int m,vector<vector<int>>&grid){for(int i=0;i<n;i++){for(int j=0;j<m;j++){grid[i][j]+=get(i,j-1,grid)+get(i-1,j,grid)-get(i-1,j-1,grid);}}}int get(int i,int j,vector<vector<int>> &grid){return i<0||j<0?0:grid[i][j];}int sum(int a,int b,int c,int d,vector<vector<int>> &grid){return get(c,d,grid)-get(c,b-1,grid)-get(a-1,d,grid)+get(a-1,b-1,grid);}
};
从这个题开始就需要不少思维量和转化了。
判断是否被1包裹,思路是,构建二维前缀和数组,用外面大正方形的累加和减去除去边长里小正方形的累加和,若等于四倍的边长,就说明被1包裹。
这里注意是在原数组上操作,所以要判断下标是否越界。
二、二维差分
1.内容
没啥好说的,就是在二维数组上的差分。
2.模板——地毯
#include<bits/stdc++.h>
using namespace std;int get(int i,int j,vector<vector<int> >&grid)
{return i<0||j<0?0:grid[i][j];
}void build(int n,vector<vector<int> >&grid)
{for(int i=0;i<=n;i++){for(int j=0;j<=n;j++){grid[i][j]+=get(i,j-1,grid)+get(i-1,j,grid)-get(i-1,j-1,grid);}}
}void add(int a,int b,int c,int d,vector<vector<int> >&grid)
{grid[a][b]++;grid[a][d+1]--;grid[c+1][b]--;grid[c+1][d+1]++;
}void solve()
{int n,m;cin>>n>>m;vector<vector<int> >grid(n+2,vector<int>(n+2,0));for(int i=0,a,b,c,d;i<m;i++){cin>>a>>b>>c>>d;add(a,b,c,d,grid);}build(n,grid);for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){cout<<grid[i][j]<<" ";}cout<<endl;}
}int main()
{solve();return 0;
}
求二维差分的方法就是,点(a,b)和(c+1,d+1)上加上k,点(c+1,b)和(a,d+1)上减去k,之后对差分数组求一遍二维前缀和即可。
3.用邮票贴满网格图
class Solution {
public:int m,n;void build(vector<vector<int>>&sum){for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];}}}int sumArea(int a,int b,int c,int d,vector<vector<int>>&sum){return sum[c][d]-sum[c][b-1]-sum[a-1][d]+sum[a-1][b-1];}void add(int a,int b,int c,int d,vector<vector<int>>&diff){diff[a][b]++;diff[c+1][b]--;diff[a][d+1]--;diff[c+1][d+1]++;}bool possibleToStamp(vector<vector<int>>& grid, int stampHeight, int stampWidth) {m=grid.size();n=grid[0].size();vector<vector<int>>sum(m+1,vector<int>(n+1,0));for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){sum[i][j]=grid[i-1][j-1];}}build(sum);vector<vector<int>>diff(m+2,vector<int>(n+2,0));for(int a=1;a<=m;a++){for(int b=1;b<=n;b++){int c=a+stampHeight-1,d=b+stampWidth-1;if(c<=m&&d<=n&&sumArea(a,b,c,d,sum)==0){add(a,b,c,d,diff);}}}build(diff);for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){if(diff[i][j]==0&&grid[i-1][j-1]==0){return false;}}}return true;}
};
这个题主要是辅助数组的使用,在辅助数组上贴邮票,最后判断其和原数组是否都为0。
首先为了判断一个范围上是否能贴,即累加和为0,要先构建一个前缀和数组。之后根据前缀和数组里的信息进行贴邮票(能贴就贴),遍历每个(a,b)点,然后根据邮票大小计算(c,d),注意判断是否越界。最后遍历和原数组进行比较,若都是0就说明这里需要贴但没贴,返回false,否则true。
三、离散化技巧——最强祝福力场
class Solution {
public:typedef long long ll;int n;void add(int a,int b,int c,int d,vector<vector<ll>>&diff){diff[a][b]++;diff[c+1][b]--;diff[a][d+1]--;diff[c+1][d+1]++;}int build(vector<vector<ll>>&diff){int ans=0;for(int i=1;i<diff.size();i++){for(int j=1;j<diff[0].size();j++){diff[i][j]+=diff[i-1][j]+diff[i][j-1]-diff[i-1][j-1];ans=ans<diff[i][j]?diff[i][j]:ans;}}return ans;}int fieldOfGreatestBlessing(vector<vector<int>>& forceField) {n=forceField.size();vector<ll>xl(2*n);vector<ll>yl(2*n);for(ll i=0,j=0,k=0;i<n;i++){ll x=forceField[i][0];ll y=forceField[i][1];ll r=forceField[i][2];//转化成整数xl[j++]=2*x-r;xl[j++]=2*x+r;yl[k++]=2*y-r;yl[k++]=2*y+r;}//离散化->排序后去重sort(xl.begin(),xl.end());sort(yl.begin(),yl.end());map<ll,ll>mx;map<ll,ll>my;map<ll,ll>::iterator iter;for(int i=0,p=1;i<xl.size();i++){iter=mx.find(xl[i]);if(iter==mx.end()){mx[xl[i]]=p++;}}for(int i=0,p=1;i<yl.size();i++){iter=my.find(yl[i]);if(iter==my.end()){my[yl[i]]=p++;}}vector<vector<ll>>diff(xl.size()+2,vector<ll>(yl.size()+2,0));for(int i=0,a,b,c,d;i<n;i++){ll x=forceField[i][0];ll y=forceField[i][1];ll r=forceField[i][2];a=mx[2*x-r];b=my[2*y-r];c=mx[2*x+r];d=my[2*y+r];add(a,b,c,d,diff);}return build(diff);}
};
在面对较大的数值,同时数值的大小本身又不重要时,可以对其进行离散化,节省空间。
首先,这个题的第一个难点是对小数的处理。因为在计算边长时会出现0.5这种小数,而根据题意,数值的大小本身并不重要,重要的是数值间的相对关系,所以考虑将其转化成整数。
再就是第二个难点,由于数值太大,不可能开一个这么大的数组,所以对其进行离散化。主要方法是,先将数组排序,之后去重,同时对其进行编号。之后,在进行差分时,只需要按编号个数构建数组,并按编号差分。这样不仅减少了数组大小,而且还不破坏力场的相对大小。这里也可以重新构建x和y数组,因为有序,所以每次用二分搜索找坐标对应的编号。
总结
确实有点烧脑了……
END