本期主要讲解二维前缀和。
知识点
我们令 \(a_{i,j}\) 表示原数组,则 \(sum_{i,j}\) 为 \(a\) 的二维前缀和数组。
根据容斥原理,得到递推式:
二维前缀和适用于求静态矩阵的子矩阵元素和。
若我需要求一个左上角坐标为 \((i,j)\)、右下角坐标为 \((x,y)\) 的子矩阵的元素和 \(S\),则再次根据容斥原理,得到公式:
例题
T1
\(O(n^4)\) 暴力枚举左上角和右下角,利用二维前缀和取元素和的 \(\max\) 即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans=-1e18;
int a[131][131],sum[131][131];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j],sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int ii=i;ii<=n;ii++)
for(int jj=j;jj<=n;jj++)
ans=max(ans,sum[ii][jj]-sum[ii][j-1]-sum[i-1][jj]+sum[i-1][j-1]);
cout<<ans;
return 0;
}
T2
首先用二维前缀和计算出每个子矩阵的点的个数。
仍然 \(O(n^4)\) 枚举左上与右下,中间留出一个长和宽都比当前矩阵小 \(1\) 的矩阵,对所有这样的大矩阵中点的个数 \(-\) 小矩阵中点的个数取 \(\max\) 即可。
#include<bits/stdc++.h>
using namespace std;
int n,ans;
int a[131][131];
int sum[131][131];
int work(int x,int y,int i,int j){
return sum[i][j]-sum[x-1][j]-sum[i][y-1]+sum[x-1][y-1];
}
int main(){
cin>>n;
for(int i=1,x,y;i<=n;i++) cin>>x>>y,a[x][y]++;
for(int i=1;i<=100;i++)
for(int j=1;j<=100;j++)
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
for(int x=1;x<=100;x++)
for(int y=1;y<=100;y++)
for(int i=x+1;i<=100;i++)
for(int j=y+1;j<=100;j++){
int out=work(x,y,i,j),in=work(x+1,y+1,i-1,j-1);
ans=max(ans,out-in);
}
cout<<ans;
return 0;
}
T3
倒序枚举边长,对于每一种边长枚举左上角坐标,利用二维前缀和求出其中 \(1\) 的个数,若 \(1\) 的个数 \(=\) 总面积,则将当前边长的正方形数 \(+ \ 1\),每种边长枚举完了输出边长及其正方形个数。
如果正序枚举则需要一个数组来保存。
#include<bits/stdc++.h>
using namespace std;
int n,sum[310][310],ans[310];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
char ch; cin>>ch;
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(ch=='1');
}
for(int i=2;i<=n;i++){
int a=i*i;
for(int x=1;x+i-1<=n;x++)
for(int y=1;y+i-1<=n;y++){
int xx=x+i-1,yy=y+i-1,s=sum[xx][yy]-sum[xx][y-1]-sum[x-1][yy]+sum[x-1][y-1];
if(s==a) ans[i]++;
}
}
for(int i=2;i<=n;i++) if(ans[i]) cout<<i<<' '<<ans[i]<<'\n';
return 0;
}
T4
仅需要在枚举出边长和左上角后,计算出当前面积并更新最优答案即可。
#include<bits/stdc++.h>
using namespace std;
int n,m,c;
int a[1031][1031],sum[1031][1031];
int ans=-1e9,xx,yy;
int main(){
cin>>n>>m>>c;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j],sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
for(int i=1;i<=n-c+1;i++)
for(int j=1;j<=m-c+1;j++){
int x=i+c-1,y=j+c-1,s=sum[x][y]-sum[x][j-1]-sum[i-1][y]+sum[i-1][j-1];
if(s>ans)
ans=s,xx=i,yy=j;
}
cout<<xx<<' '<<yy;
return 0;
}
T5
和 T3 几乎一致,只是不需要输出正方形个数。
#include<bits/stdc++.h>
using namespace std;
int n,m,ans=-1e9;
int a[131][131],sum[131][131];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j],sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
for(int i=1;i<=min(n,m);i++){
int ar=i*i;
for(int j=1;j+i-1<=n;j++){
for(int k=1;k+i-1<=m;k++){
int x=j+i-1,y=k+i-1,s=sum[x][y]-sum[x][k-1]-sum[j-1][y]+sum[j-1][k-1];
if(s==ar) ans=max(ans,i);
}
}
}
cout<<ans;
return 0;
}
习题
T6
T1 的双倍经验,只是需要输出 \n
!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans=-1e18;
int a[131][131],sum[131][131];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j],sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int ii=i;ii<=n;ii++)
for(int jj=j;jj<=n;jj++)
ans=max(ans,sum[ii][jj]-sum[ii][j-1]-sum[i-1][jj]+sum[i-1][j-1]);
cout<<ans<<'\n';
return 0;
}
T7
仍然枚举左上和右下,对所有子矩阵中 \(1\) 的数量取 \(\min\) 即可。
另外这题有个坑,就是矩形不一定为 \(a \times b\),还有可能为 \(b \times a\),因此对于两种情况都要枚举一边。
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,ans=1e9+31;
int a[131][131],sum[131][131];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j],sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
cin>>x>>y;
//cout<<sum[1][2]-sum[1][1]-sum[0][2]+sum[0][1]<<'\n';
for(int i=1;i+x-1<=n;i++)
for(int j=1;j+y-1<=m;j++){
int ii=i+x-1,jj=j+y-1,s=sum[ii][jj]-sum[ii][j-1]-sum[i-1][jj]+sum[i-1][j-1];
ans=min(ans,s);
}
for(int i=1;i+y-1<=n;i++)
for(int j=1;j+x-1<=m;j++){
int ii=i+y-1,jj=j+x-1,s=sum[ii][jj]-sum[ii][j-1]-sum[i-1][jj]+sum[i-1][j-1];
ans=min(ans,s);
}
cout<<ans;
return 0;
}
T8
首先,因为空间十分紧张,所以我们直接向 \(sum\) 数组读入并进行预处理二维前缀和。
然后我们枚举 \(m \times m\) 的矩形的右下角,求出目标的价值总和取 \(\max\) 即为答案。
同时,我们考虑将目标的点的 \((x,y)\) 均加上 \(0.5\),即让其处于格子的中间,使得我们可以不考虑恰好在正方形边上的点。
#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
int maxx,maxy;
int sum[5031][5031];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int x,y,v;
cin>>x>>y>>v;
sum[x+1][y+1]+=v;
maxx=max(maxx,x+1),maxy=max(maxy,y+1);
}
for(int i=1;i<=5001;i++)
for(int j=1;j<=5001;j++)
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
for(int i=m;i<=5001;i++)
for(int j=m;j<=5001;j++)
ans=max(ans,sum[i][j]-sum[i][j-m]-sum[i-m][j]+sum[i-m][j-m]);
cout<<ans;
return 0;
}