【题解】51nod 1158 全是1的最大子矩阵

2023-10-22 01:58

本文主要是介绍【题解】51nod 1158 全是1的最大子矩阵,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目传送门
题意:
给出 1 1 1 n × m n\times m n×m的矩阵 M 1 M_1 M1,里面的元素只有 0 0 0 1 1 1,找出 M 1 M_1 M1 的一个子矩阵 M 2 M_2 M2 M 2 M_2 M2 中的元素只有1,并且 M 2 M_2 M2 的面积是最大的。输出 M 2 M_2 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 6 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 6 6,也就是右上角长为 2 2 2,高为 3 3 3 的矩形。但我的程序输出了 5 5 5。看到第三行,原来在计算第三行第三到五个数组成的段时,第三个数的值为 1 1 1,所以,后面的高度就被锁定成了 1 1 1,而忽略了后面 3 3 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 1 1 个点:# 7 7 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 114 114 行(含注释 118 118 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 n\times m n×m的矩阵,枚举每一个点, i i i是从 1 1 1 n n n j j j 又是从 1 1 1 n n n
    自己改吧。(居然还 AC 了,不可思议)
  2. 既然都已经发现了勘误 1 1 1,那么在时间复杂度的分析里,从上往下扫和从下往上扫的时间复杂度都应该是 O ( 2 n m ) O(2nm) O(2nm) ,对总时间复杂度无伤大雅,但毕竟还是要严谨的。

这篇关于【题解】51nod 1158 全是1的最大子矩阵的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/258361

相关文章

poj 3723 kruscal,反边取最大生成树。

题意: 需要征募女兵N人,男兵M人。 每征募一个人需要花费10000美元,但是如果已经招募的人中有一些关系亲密的人,那么可以少花一些钱。 给出若干的男女之间的1~9999之间的亲密关系度,征募某个人的费用是10000 - (已经征募的人中和自己的亲密度的最大值)。 要求通过适当的招募顺序使得征募所有人的费用最小。 解析: 先设想无向图,在征募某个人a时,如果使用了a和b之间的关系

poj 3258 二分最小值最大

题意: 有一些石头排成一条线,第一个和最后一个不能去掉。 其余的共可以去掉m块,要使去掉后石头间距的最小值最大。 解析: 二分石头,最小值最大。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <c

poj 2175 最小费用最大流TLE

题意: 一条街上有n个大楼,坐标为xi,yi,bi个人在里面工作。 然后防空洞的坐标为pj,qj,可以容纳cj个人。 从大楼i中的人到防空洞j去避难所需的时间为 abs(xi - pi) + (yi - qi) + 1。 现在设计了一个避难计划,指定从大楼i到防空洞j避难的人数 eij。 判断如果按照原计划进行,所有人避难所用的时间总和是不是最小的。 若是,输出“OPETIMAL",若

poj 2135 有流量限制的最小费用最大流

题意: 农场里有n块地,其中约翰的家在1号地,二n号地有个很大的仓库。 农场有M条道路(双向),道路i连接着ai号地和bi号地,长度为ci。 约翰希望按照从家里出发,经过若干块地后到达仓库,然后再返回家中的顺序带朋友参观。 如果要求往返不能经过同一条路两次,求参观路线总长度的最小值。 解析: 如果只考虑去或者回的情况,问题只不过是无向图中两点之间的最短路问题。 但是现在要去要回

poj 2594 二分图最大独立集

题意: 求一张图的最大独立集,这题不同的地方在于,间接相邻的点也可以有一条边,所以用floyd来把间接相邻的边也连起来。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <sta

hdu 4565 推倒公式+矩阵快速幂

题意 求下式的值: Sn=⌈ (a+b√)n⌉%m S_n = \lceil\ (a + \sqrt{b}) ^ n \rceil\% m 其中: 0<a,m<215 0< a, m < 2^{15} 0<b,n<231 0 < b, n < 2^{31} (a−1)2<b<a2 (a-1)^2< b < a^2 解析 令: An=(a+b√)n A_n = (a +

poj 3422 有流量限制的最小费用流 反用求最大 + 拆点

题意: 给一个n*n(50 * 50) 的数字迷宫,从左上点开始走,走到右下点。 每次只能往右移一格,或者往下移一格。 每个格子,第一次到达时可以获得格子对应的数字作为奖励,再次到达则没有奖励。 问走k次这个迷宫,最大能获得多少奖励。 解析: 拆点,拿样例来说明: 3 2 1 2 3 0 2 1 1 4 2 3*3的数字迷宫,走两次最大能获得多少奖励。 将每个点拆成两个

poj 3692 二分图最大独立集

题意: 幼儿园里,有G个女生和B个男生。 他们中间有女生和女生认识,男生男生认识,也有男生和女生认识的。 现在要选出一些人,使得这里面的人都认识,问最多能选多少人。 解析: 反过来建边,将不认识的男生和女生相连,然后求一个二分图的最大独立集就行了。 下图很直观: 点击打开链接 原图: 现图: 、 代码: #pragma comment(

最大流、 最小费用最大流终极版模板

最大流  const int inf = 1000000000 ;const int maxn = 20000 , maxm = 500000 ;struct Edge{int v , f ,next ;Edge(){}Edge(int _v , int _f , int _next):v(_v) ,f(_f),next(_next){}};int sourse , mee

二分最大匹配总结

HDU 2444  黑白染色 ,二分图判定 const int maxn = 208 ;vector<int> g[maxn] ;int n ;bool vis[maxn] ;int match[maxn] ;;int color[maxn] ;int setcolor(int u , int c){color[u] = c ;for(vector<int>::iter