修建泳池&最大子矩阵

【题目描述】
夏天到了,学校打算在教学楼后面的空地上挖一个泳池供大家使用。
经过实地勘察,这块土地可以划分成N 行M 列的方格,有的方格是树,有的方格是空地。现在要找一块最大的矩形空地修建泳池,请问泳池的面积有多大?

【输入】
第一行两个正整数N, M,分别表示土地的行数和列数。
接下来N行,每行有M个用空格隔开的数a[i][j],每个数均为0或1,0表示空地,1表示树。

【输出】
输出一行一个整数,表示最大的泳池面积。

【样例输入】

样例输入1
4 5
0 1 0 1 0
0 0 0 0 0
0 0 0 0 1
1 0 0 0 0

样例输入2
4 5
1 1 0 1 1
1 1 0 0 1
1 0 0 0 0
0 0 0 0 0

【样例输出】

样例输出1
9

样例输出2
8

【数据范围限制】
对于10%的数据,所有的0构成一个纯天然的长方形区域
对于30%的数据,所有的0构成若干个互不相交的有树隔开的长方形区域
对于另外30%的数据,所有的0构成一个柱状图,即每一列所有的1都在该列的最上方
对于100%的数据,1<=N, M<=2000

【提示】

样例解释1
泳池的左上角为(2,2),右下角为(4,4),面积为9。

样例解释2
所有的0构成一个柱状图,即每一列所有的1都在该列的最上方,泳池的左上角为(3,2),右下角为(4,5),面积为8。这种情况泳池的右下角一定在最后一行!

【题解】

这题还是蛮WS的!题目其实就是让我们求出由0构成的最大矩阵面积。
这题真不愧是“水分容易AC难”。比赛时,我就打了一个暴力程序,枚举矩阵的左上角和右下角,再用个前缀和维护一下,然后便水到了60分。

其实这题我们可以用一种O(nm)的方法来做。
首先,我们可以定义3个重要的数组:

  1. 我们要定义L数组,大小是n*m的。l[i][j]表示第i行第j列的矩阵元素所在的最大矩阵中,第i行第j列的矩阵元素左边(包括它自己)有多少个连续的0 。

  2. 我们还要定义一个R数组,大小和L数组一样。r[i][j]表示第i行第j列的矩阵元素所在的矩阵中,第i行第j列的矩阵元素右边(包括它自己)有多少个连续的0 。

  3. 有了L数组和R数组,我们还要定义一个什么数组呢?当然是up数组啦!up[i][j]表示第i行第j列所在的最大矩阵中,第i行第j列的矩阵元素上面(包括它自己)有多少个连续的0 。

但是,光是定义还不够,我们还必须了解它们怎么用:

  • 我们可以用这三个数组来更新答案:

      - $ans=max(ans,(l[i][j]+r[i][j]-1) * up[i][j]);$
    
  • 我们怎么求出它们的值呢?我们必须知道:

第i行第j列的矩阵元素所在的最大矩阵的列的长度一定是等于第i行第j列的矩阵元素的上方最小的列的长度(也就是说,$l_{i,j}= \min(l_{i-k,j}) (k<up_{i,j}),r_{i,j}=\min(r_{i-k,j}) (k<up_{i,j}) $)
千万别担心这样求出来的答案不会是最小值。比如说下面这种情况:(红色表示1,蓝色表示0)
-

 - 在这种情况中:虽然在第4行第3列这一个格子中,所求出的矩阵只是3*1大小的(答案明显是最后一行的那个矩阵),但是,第4行的其它格子求出来的正是这个矩阵呀!绝对会有一些格子求出你想要的答案的。

在打这道题的代码时,思路一定要清晰,不然就会出错哟~

#include<cstdio>
#include<cstring>
using namespace std;
int a[2010][2010],up[2010][2010],l[2010][2010],r[2010][2010];
int mymin(int x,int y)
{
	if(x<y) return x;
	else return y;
}
int main()
{
	int n,m,i,j,k,t,ans=0;
	scanf("%d%d",&n,&m);
	memset(a,1,sizeof(a));
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			if(a[i][j]==0)
			{
				if(a[i][j-1]==0) l[i][j]=l[i][j-1]+1;
				else l[i][j]=1;
			}
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=m;j>0;j--)
		{
			if(a[i][j]==0)
			{
				if(a[i][j+1]==0) r[i][j]=r[i][j+1]+1;
				else r[i][j]=1;
			}
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			if(a[i][j]==0)
			{
				if(a[i-1][j]==0)
				{
					up[i][j]=up[i-1][j]+1;
					if(a[i-1][j]==0)
					{
						l[i][j]=mymin(l[i][j],l[i-1][j]);
						r[i][j]=mymin(r[i][j],r[i-1][j]);
					}
				}
				else
				{
					up[i][j]=1;
				}
			}
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			t=(r[i][j]+l[i][j]-1)*up[i][j];
			if(ans<t) ans=t;
		}
	}
	printf("%d\n",ans);
	return 0;
}

加强版

【NOIP2015模拟10.22】最大子矩阵

题目

【题目描述】
我们将矩阵A中位于第i行第j列的元素记作A[i,j]。一个矩阵A是酷的仅当它满足下面的条件:
\(A[1,1]+A[r,s]<=A[1,s]+A[r,1](r,s>1)\)
其中r为矩阵A的行数,s为矩阵A的列数。
进一步,如果一个矩阵是非常酷的仅当它的每一个至少包含两行两列子矩阵都是酷的。
你的任务是,求出一个矩阵A中的一个非常酷的子矩阵B,使得B包含最多元素。
【输入】
第一行包含两个整数R,S(2<=R,S<=1000),代表矩阵的行数与列数。
接下来R行每行包括S个整数,代表矩阵中的元素,矩阵中元素的绝对值不大于1000000。
【输出】
一行一个整数,代表子矩阵B的元素总数。如果没有一个非常酷的子矩阵,输出0。
【样例输入】
输入1:
3 3
1 4 10
5 2 6
11 1 3
输入2:
3 3
1 3 1
2 1 2
1 1 1
输入3:
5 6
1 1 4 0 3 3
4 4 9 7 11 13
-3 -1 4 2 8 11
1 5 9 5 9 10
4 8 10 5 8 8
【样例输出】
输出1:
9
输出2:
4
输出3:
15
样例3解释
在第三个样例中,子矩阵B的左上角为A[3,2],右下角为A[5,6]。
【数据范围限制】
对于60%的数据,满足R,S<=350。
对于100%的数据,满足2<=R,S<=1000,矩阵中元素的绝对值不大于1000000。


题解

可以证明得出:
当一个矩阵A中包含的所有的22的矩形都是酷的时,A非常酷。
因此,我们只需要判断A中的每一个2
2的矩形酷不酷即可。
然而这样还是会时超。。。

我们可以看一下判断的条件:

\[A_{1,1}+A_{r,s}\leq A_{1,s}+A_{r,1}(r,s>1) \]

移项,变成:

\[A_{1,1}-A_{1,s}\leq A_{r,1}-A_{r,s}(r,s>1) \]

也就是

\[A_{1,1}-A_{1,s}-(A_{r,1}-A_{r,s})\leq 0(r,s>1) \]

由于我们只用判断2*2的矩形,因此(设(i,j)是该矩形的左上角)

\[(A_{i,j}-A_{i,j+1})-(A_{i+1,j}-A_{i+1,j+1})\leq 0 \]

可以发现括号内的值没什么关系,可以预处理。所以我们可以新建一个矩阵B,其中

\[B_{i,j}=A_{i,j}-A_{i,j+1} \]

再新建一个C数组,其中

\[\begin{aligned}C_{i,j}&=(A_{i,j}-A_{i,j+1})-(A_{i+1,j}-A_{i+1,j+1})\\&=B_{i,j}-B_{i+1,j}\end{aligned} \]

然后再新建一个数组D,如果\(C_{i,j}\le 0\),那么\(D_{i,j}=0\)
然后这个问题就被转化为修建泳池了(注意:计算答案时,最大矩阵的行、列都要+1)。


加强版

#include<cstdio>
using namespace std;
int a[1010][1010],up[1010][1010],left[1010][1010],right[1010][1010];
bool b[1010][1010];
int main()
{
	freopen("max.in","r",stdin);
	freopen("max.out","w",stdout);
	int n,m,i,j,k,ans=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			scanf("%d",&a[i][j]);
	for(i=1;i<=n;i++)
		for(j=1;j<m;j++)
			a[i][j]-=a[i][j+1];
	for(i=1;i<n;i++)
	{
		for(j=1;j<m;j++)
		{
			a[i][j]-=a[i+1][j];
			if(a[i][j]<=0)
			{
				b[i][j]=1;
				if(b[i][j-1]) left[i][j]=left[i][j-1]+1;
				else left[i][j]=1;
			}
		}
		for(j=m-1;j>0;j--)
			if(b[i][j])
			{
				if(b[i][j+1]) right[i][j]=right[i][j+1]+1;
				else right[i][j]=1;
			}
		for(j=1;j<m;j++)
			if(b[i][j])
			{
				if(b[i-1][j])
				{
					up[i][j]=up[i-1][j]+1;
					if(left[i][j]>left[i-1][j]) left[i][j]=left[i-1][j];
					if(right[i][j]>right[i-1][j]) right[i][j]=right[i-1][j];
				}
				else up[i][j]=1;
				k=(left[i][j]+right[i][j])*(up[i][j]+1);
				if(k>ans) ans=k;
			}
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-07-08 21:44  Alexander_菜鸡  阅读(228)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end