2019牛客多校训练第二场H. Second Large Rectangle (单调栈 / 悬线法)

题目传送门

题意

输入整数n和m(n和m均∈[1,1000]),给出一个n×m的01矩阵,求第二大全1矩阵的面积。(如果矩阵少于两个1,则输出0)

题解

①单调栈做法【复杂度O(n×m)】先预处理每个点(i,j)的高up[i][j],枚举每一行作为底边,遍历每一个点j,维护单调递减栈计算当前高度可达的最大区间(即当前可形成的全1矩阵的最大宽),宽×高即得当前可形成的全1矩形的面积,此时如果记录最大面积然后不断更新最优解即可求得所给01矩阵的最大全1矩阵面积。

但是题目要求的是第二大,所以本蒟蒻天真的认为只要把所有面积记录到一个数组,之后partial_sort排下序后输出第二大就OK了。too young too simple~

在经历过一次次WA之后去查了下,说是会存在第二大矩阵只包含在最大矩阵里面这种情况,奈何构造不出符合情况的测试用例来验证所以不太理解。最终去写了个生成随机测试用例,拿AC代码对拍了下,发现了如下两个测试用例存在错误。

6 6             2 24
100101   正确答案:5   101010001011100001001101     正确答案:5
011011   我的答案:4    110111111001001100010101      我的答案:4
111111
010100
011010
001110

所以要求第二大的矩阵面积,除了宽×高算面积外,还要再多算两种情况:(宽-1)×高,宽×(高-1)。

Code

/*133ms*/
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1005; int a[maxn][maxn],up[maxn][maxn],ans[3*maxn*maxn];//注意这里要3*maxn*maxn int main() { int n,m; while(~scanf("%d%d",&n,&m)){ memset(up,0,sizeof(up)); memset(a,0,sizeof(a)); memset(ans,0,sizeof(ans)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%1d",&a[i][j]); up[i][j]=(a[i][j]==1)?up[i-1][j]+1:0; } } int area,q=0; for(int i=1;i<=n;i++){ stack<pair<int,int> >S; for(int j=1;j<=m+1;j++){ int L=j; while(!S.empty()&&up[i][j]<=S.top().first){ L=S.top().second; area=(j-L)*S.top().first; ans[q++]=area; area=(j-L-1)*S.top().first; ans[q++]=area; area=(j-L)*(S.top().first-1); ans[q++]=area; S.pop(); } if(S.empty()||up[i][j]>S.top().first) S.push({up[i][j],L}); } } partial_sort(ans,ans+2,ans+q,greater<int>()); cout<<ans[1]<<endl; } return 0; }

 求面积次大值除了部分排序或优先队列外,还可如下操作:

1 void calc(int area)//求次大值(max1存最大值,max2存次大值)
2 {
3     if(area>max1){
4         max2=max1;
5         max1=area;
6     }
7     else if(area>max2)
8         max2=area;
9 }

 

②悬线法【复杂度O(n×m)】一般用来求解满足条件的最大子矩阵。(浅谈用极大化思想解决最大子矩阵问题

悬线:上端点覆盖了一个障碍点或达到整个矩形上端的有效竖线。每条悬线向左和向右能到达的最远的点之间距离×悬线长度即为以这条悬线下端点为下边界的极大子矩阵。

故该题用悬线法思路如下:

对于每个点1,先L(i,j)预处理每个点1的第一个左障碍点0,R(i,j)预处理每个点1第一个右障碍点0,up(i,j)预处理每个点的高度;

对于每个点进行状态转移:L(i,j)=max(L(i,j),L(i−1,j)) 该点左边第一个障碍点位置,边界也是障碍点
            R(i,j)=min(R(i,j),R(i−1,j)) 该点右边第一个障碍点位置,边界也是障碍点 

故对于点(i,j)为底的悬线对应的子矩形,它的面积area=(R[i,j]-L[i,j]+1)*up[i,j]。

至于如何求次大矩形面积,详见以下代码。

Code:

/*149ms*/
#include<bits/stdc++.h> using namespace std; const int maxn=1e3+5; int a[maxn][maxn],up[maxn][maxn],L[maxn][maxn],R[maxn][maxn]; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%1d",&a[i][j]); if(a[i][j]==0)continue; up[i][j]=up[i-1][j]+1;//预处理每个点的高 L[i][j]=(a[i][j-1]==1)?L[i][j-1]:j;//预处理每个点1的左边第一个障碍点0 } for(int j=m;j>=1;j--){ if(a[i][j]==0)continue; R[i][j]=(a[i][j+1]==1)?R[i][j+1]:j;//预处理每个点1右边的第一个障碍点0 } } int area,max1=0,r,l,u,b;//max1存最大矩形面积 for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ area=0; if(a[i][j]==0)continue; if(a[i-1][j]==1){ R[i][j]=min(R[i][j],R[i-1][j]); L[i][j]=max(L[i][j],L[i-1][j]); } area=(R[i][j]-L[i][j]+1)*up[i][j];//宽×高求得可形成的矩形面积 max1=max(max1,area);//更新面积的最大值 if(max1==area) r=R[i][j],l=L[i][j],u=up[i][j],b=i;//记录最大矩形的信息 } } int ans2=0; ans2=max(ans2,(r-l)*u);//特判最大的矩形少一行和少一列的情况 ans2=max(ans2,(r-l+1)*(u-1)); for(int i=1;i<=n;i++){ //求次大矩形面积 for(int j=1;j<=m;j++){ if(u==up[i][j]&&l==L[i][j]&&r==R[i][j]&&b==i)//跳过最大矩形 continue; area=(R[i][j]-L[i][j]+1)*up[i][j]; if(ans2<area) ans2=area; } } cout<<ans2<<endl;//输出次大矩形面积 return 0; }

 

转载于:https://www.cnblogs.com/HOLLAY/p/11355539.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值