51nod 1158 全是1的最大子矩阵

题目传送门
题意:
给出1个M*N的矩阵M1,里面的元素只有0或1,找出M1的一个子矩阵M2,M2中的元素只有1,并且M2的面积是最大的。输出M2的面积。

这道题,是一道十分玄学的题目。
在题目上方有两个标签——“单调栈”和“DP”。但实际上,这道题根本就不需要单调栈或是DP。
因为我不会,所以我只会玄学
首先,我们随便拿一组数据(样例):

1 1 0
1 1 1
0 1 1

我们把每一个1上方的连续的1的数量写一下:

1 1 0
2 2 1
0 3 2

那么好了,我们一行行来。首先看第一行,在第一行及其上方(其实也就是第一行)的最大全1矩形面积为2,第二行及其上方的最大全1矩阵面积为4,第三行及其上方的最大全1矩阵面积同样为4。所以整个矩阵的最大全1矩阵面积为4。
这个面积的求法,对于该行每一个连续的非0段,统计它的长度和最小值(也就是高度),求出面积,取所有段的最大值即可。
想到这里的我,愉快的写了一份代码,交了上去。
提交链接1

#include<bits/stdc++.h>
using namespace std;
int a[505][505];
int main()
{
	int n,m,ans=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			if(a[i][j]==1) a[i][j]=a[i-1][j]+1;//统计在这个点上面连续的1的数量(包括它自己)
		}
	for(int i=1;i<=n;i++)
	{
		int nowlen=0,maxlen=0,nowhig=1<<30,maxhig=0;//nowlen当前段长度,nowhig当前段最小值(高度),maxlen和maxhig是面积最大的段的长度和高度
		for(int j=1;j<=n;j++)
		{
			if(!a[i][j]&&a[i][j-1])//每一个段结束后把当前段的长度和高度清零
			{
				nowlen=0;
				nowhig=1<<30;
			}
			if(a[i][j])
			{
				nowlen++;
				nowhig=min(nowhig,a[i][j]);
				if(nowlen*nowhig>maxlen*maxhig)//实时更新面积
				{
					maxlen=nowlen;
					maxhig=nowhig;
				}
			}
		}
		ans=max(ans,maxlen*maxhig);
	}
	printf("%d",ans);
	return 0;
}

然后呢?
WA了6个点。
我就想,这道题真的只能用单调栈或是DP吗?
于是,我下了一组数据。

0 0 0 1 1
1 1 0 1 1
0 0 1 1 1
0 0 1 1 0
0 1 0 1 0

正解是6,也就是右上角长为2,高为3的矩形。但我的程序输出了5。看到第三行,原来在计算第三行第三到五个数组成的段时,第三个数的值为1,所以,后面的高度就被锁定成了1,而忽略了后面3的高度。
怎么办?
我的原程序是从左往右扫,于是我想到了一个玄学的方法:
从左往右扫,再从右往左扫
想到这里的我,愉快的写了一份代码,交了上去。
提交链接2

#include<bits/stdc++.h>
using namespace std;
int a[505][505];
int main()
{
	int n,m,ans=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			if(a[i][j]==1) a[i][j]=a[i-1][j]+1;
		}
	for(int i=1;i<=n;i++)
	{
		int nowlen=0,maxlen=0,nowhig=1<<30,maxhig=0;
		for(int j=1;j<=n;j++)
		{
			if(!a[i][j]&&a[i][j-1])
			{
				nowlen=0;
				nowhig=1<<30;
			}
			if(a[i][j])
			{
				nowlen++;
				nowhig=min(nowhig,a[i][j]);
				if(nowlen*nowhig>maxlen*maxhig)
				{
					maxlen=nowlen;
					maxhig=nowhig;
				}
			}
		}
		ans=max(ans,maxlen*maxhig);
		//从右往左扫
		nowlen=0,maxlen=0,nowhig=1<<30,maxhig=0;
		for(int j=n;j>=1;j--)
		{
			if(!a[i][j]&&a[i][j+1])
			{
				nowlen=0;
				nowhig=1<<30;
			}
			if(a[i][j])
			{
				nowlen++;
				nowhig=min(nowhig,a[i][j]);
				if(nowlen*nowhig>maxlen*maxhig)
				{
					maxlen=nowlen;
					maxhig=nowhig;
				}
			}
		}
		ans=max(ans,maxlen*maxhig);
	}
	printf("%d",ans);
	return 0;
}

然后呢?
WA了1个点:#7。
其实这个结果,在我交之前,早就已经想到了。我还知道它们会出什么样的数据来卡我这个程序。例如

0 1 1 1 0
0 1 1 1 0
0 1 1 1 0
0 1 1 1 0
1 1 1 1 1

在最后一行,不管你是从左往右扫,还是从右往左扫,都会在第一个数字锁死高度,因为它是对称的。
于是,我又想到了一个更加玄学的方法:
先从上往下扫,再从下往上扫。
这样就可以完美处理这个毒瘤数据。
想到这里的我,愉快的写了一份代码,交了上去。
提交链接3

#include<bits/stdc++.h>
using namespace std;
int a[505][505];
int main()
{
	int n,m,ans=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			if(a[i][j]==1)
				a[i][j]=a[i-1][j]+1;
		}
	for(int i=1;i<=n;i++)
	{
		int nowlen=0,maxlen=0,nowhig=1<<30,maxhig=0;
		for(int j=1;j<=n;j++)
		{
			if(!a[i][j]&&a[i][j-1])
			{
				nowlen=0;
				nowhig=1<<30;
			}
			if(a[i][j])
			{
				nowlen++;
				nowhig=min(nowhig,a[i][j]);
				if(nowlen*nowhig>maxlen*maxhig)
				{
					maxlen=nowlen;
					maxhig=nowhig;
				}
			}
		}
		ans=max(ans,maxlen*maxhig);
		nowlen=0,maxlen=0,nowhig=1<<30,maxhig=0;
		for(int j=n;j>=1;j--)
		{
			if(!a[i][j]&&a[i][j+1])
			{
				nowlen=0;
				nowhig=1<<30;
			}
			if(a[i][j])
			{
				nowlen++;
				nowhig=min(nowhig,a[i][j]);
				if(nowlen*nowhig>maxlen*maxhig)
				{
					maxlen=nowlen;
					maxhig=nowhig;
				}
			}
		}
		ans=max(ans,maxlen*maxhig);
	}
	//还原矩阵
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i][j])
				a[i][j]=1;
	//矩阵翻转
	for(int i=1;i<=n/2;i++)
		for(int j=1;j<=m;j++)
			swap(a[i][j],a[n-i+1][j]);
	//计算其上连续1个数
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i][j])
				a[i][j]=a[i-1][j]+1;
	//反着扫
	for(int i=1;i<=n;i++)
	{
		int nowlen=0,maxlen=0,nowhig=1<<30,maxhig=0;
		for(int j=1;j<=n;j++)
		{
			if(!a[i][j]&&a[i][j-1])
			{
				nowlen=0;
				nowhig=1<<30;
			}
			if(a[i][j])
			{
				nowlen++;
				nowhig=min(nowhig,a[i][j]);
				if(nowlen*nowhig>maxlen*maxhig)
				{
					maxlen=nowlen;
					maxhig=nowhig;
				}
			}
		}
		ans=max(ans,maxlen*maxhig);
		nowlen=0,maxlen=0,nowhig=1<<30,maxhig=0;
		for(int j=n;j>=1;j--)
		{
			if(!a[i][j]&&a[i][j+1])
			{
				nowlen=0;
				nowhig=1<<30;
			}
			if(a[i][j])
			{
				nowlen++;
				nowhig=min(nowhig,a[i][j]);
				if(nowlen*nowhig>maxlen*maxhig)
				{
					maxlen=nowlen;
					maxhig=nowhig;
				}
			}
		}
		ans=max(ans,maxlen*maxhig);
	}
	printf("%d",ans);
	return 0;
}

然后呢?
震惊!!!
这个114行(含注释118行)的代码成功的AC了!!!
要什么单调栈呀,要什么DP呀,上下左右各扫几遍就行了。

对于这个算法的时间复杂度,输入需要 O ( n m ) O(nm) O(nm) ,从上往下扫需要 O ( 2 n ² ) O(2n²) O(2n²),还原矩阵需要 O ( n m ) O(nm) O(nm) ,翻转矩阵需要 O ( n m 2 ) O(\frac{nm}{2}) O(2nm) ,重新计算需要 O ( n m ) O(nm) O(nm),从下往上扫需要 O ( 2 n ² ) O(2n²) O(2n²),由于n和m是同一个数量级的,所以总时间复杂度为 O ( n m ) O(nm) O(nm),可能常数会比较大(因为上下左右都在扫),但对于500的数据来说,已经完全足够了。

by 232323 in 51nod

参考文献:
51nod 1158 全是1的最大子矩阵 (单调栈) 详细图解

勘误:

  1. 当你们看我的代码时,是不是感觉有点不太对劲?
    一个n*m的矩阵,枚举每一个点,i是从1到n,j又是从1到n。
    自己改吧。(居然还AC了,不可思议)
  2. 既然都已经发现了勘误1,那么在时间复杂度的分析里,从上往下扫和从下往上扫的时间复杂度都应该是 O ( 2 n m ) O(2nm) O(2nm) ,对总时间复杂度无伤大雅,但毕竟还是要严谨的。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值