Living-Dream 系列笔记 第13期

本期主要讲解二维前缀和。

知识点

我们令 \(a_{i,j}\) 表示原数组,则 \(sum_{i,j}\)\(a\) 的二维前缀和数组。

根据容斥原理,得到递推式:

\[sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+a_{i,j} \]

二维前缀和适用于求静态矩阵的子矩阵元素和。

若我需要求一个左上角坐标为 \((i,j)\)、右下角坐标为 \((x,y)\) 的子矩阵的元素和 \(S\),则再次根据容斥原理,得到公式:

\[S=sum_{x,y}-sum_{x,j-1}-sum_{i-1,y}+sum_{i-1,j-1} \]

例题

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;
}
posted @ 2024-03-09 12:27  _XOFqwq  阅读(1)  评论(0编辑  收藏  举报