P1387 最大正方形 题解

 题目描述

在一个 $n\times m$ 的只包含 0 和 1 的矩阵里找出一个不包含 0 的最大正方形,输出边长。

 输入格式

输入文件第一行为两个整数 n,m1\leq n,m\leq 100 ),接下来 n 行,每行 m 个数字,用空格隔开,01

 输出格式

一个整数,最大正方形的边长。

 样例 

样例输入 

4 4
0 1 1 1
1 1 1 0
0 1 1 0
1 1 0 1

样例输出 

2

题目传送门(题目源自洛谷)

我不知道前缀和算不算正解,但既然 $AC$ 了就写一篇题解。

正所谓暴力出奇迹. . . . . .其实我是看到 $1 \le n,m \le 100$ 才想到暴力的,但 $39ms$ 在我意料之外,吸氧之后就 $34ms$

 前置知识

前缀和 ——— 从一维到二维

基本概念

一维前缀和,在一个一维数组中任意区间内元素之和。

二位前缀和,在一个二维矩阵中任意矩阵内元素之和。

实现方式

对于一维前缀和,用一个一维数组记录每一位 1 到该位元素的和,即 sum[i]=a[1]+a[2]+\dots+a[i]

在询问时可以以 O(1) 的时间复杂度输出答案,即 sum[R]-sum[L-1],其中 L 表示区间左边界,R 表示区间右边界。举个例子方便理解:

a[0-10]  ={ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10}
sum[0-10]={ 0, 1, 3, 6,10,15,21,28,36,45,55}
a数组为初始数组
sum[i]记录a数组中区间[1,i]内所有元素之和
如果题目询问的左边界并不是1,就用sum[L]减去左边界以前的所有元素和,即减sum[L-1]
比如[3,5]的区间和为:sum[5]-sum[3-1]=15-3=12,3+4+5=12,完全正确

代码实现如下:

scanf("%d%d",&n,&q);
//n表示a数组长度,q为询问次数
for(int i=1;i<=n;i++){
    scanf("%d",a[i]);
    sum[i]=sum[i-1]+a[i];
    //在上一个前缀和的基础上加入刚输入的数
}
for(int i=1;i<=q;i++){
    scanf("%d%d",&L,&R);
    printf("%d\n",sum[R]-sum[L-1]);
}

对于二维前缀和, 用一个二维数组记录矩阵中每一点作为矩阵右下角,(1,1) 作为矩阵左上角构成的小矩阵中所有元素之和,下面用一张图表示:

图中绿色阴影中的部分即为 sum[2][3] 所覆盖中的小矩阵,同样的:如何询问的矩阵左上角并不在 (1,1) 的位置该如何解决?考虑仍然用相减的方法,O(1) 的时间复杂度输出答案。

下面画图帮助理解:

上图为求左上角为 (1,1) 右下角为 (2,3) 构成小矩阵内元素和的计算划分图,可以表示为 sum[2][ 3 ] = sum[ 2 ][ 2 ] + sum[ 1 ][ 3 ] - sum[ 1 ][ 2 ] + a[ 2 ][ 3 ]

对减去 sum[1][2] 做出解释:由于我们在求 sum[ 2 ][ 2 ] 和 sum[ 1 ][ 3 ] 时会求和两次 sum[ 1 ][ 2 ], 所以我们需要再减去一次 sum[ 1 ][ 2 ]

根据上面的运算推式子得出:sum[xb][yb]-sum[xa-1][yb]-sum[xb][ya-1]+sum[xa-1][ya-1]),其中 (xa,ya) 为小矩阵左上角,(xb,yb) 为小矩阵右上角。代码实现如下:

for(int i=1;i<=n;i++)
    for(int j=1,x;j<=m;j++){
        scanf("%d",&x);
        //sum[][]记算前缀和 
        sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+x;
    }
for(int i=1,xa,xb,ya,yb;i<=q;i++){
    scanf("%d%d%d%d",&xa,&ya,&xb,&yb);
    printf("%d\n",sum[xb][yb]-sum[xa-1][yb]-sum[xb][ya-1]+sum[xa-1][ya-1]);
}
​

 思路

题目让我们求一个不包含 $0$ 的最大正方形,输出边长。

不包含 $0$ ,也就意味着这个正方形内部全是 $1$,那这个正方形的面积就是边长乘边长了。这个正方形内部但凡有一个 $0$ ,面积就会变小,那么要确定正方形内部有没有 $0$ ,直接在输入时求出前缀和,然后和正确的正方形面积比对一下不就好了吗?!正确的正方形面积指在内部没有 $0$ 的情况下正方形的面积,即边长乘边长。

到这时本题唯一的难点就解决了,接下来就只需要考虑枚举的问题,最简单的就是枚举每个点,每次以这个点为正方形的左上角,然后再设一重循环枚举正方形的边长:

1.   如果正方形的范围超出了矩阵,就结束这层循环

2.   否则判断正方形内部是否有 $0$ ,如果没有就更新最大值 maxn

 完整代码

#include<bits/stdc++.h>
using namespace std;
//快读,可加可不加
inline int read(){
    int x;char ch;
    while(true){
        ch=getchar();
        if(ch>='0'&&ch<='9') break;
    }x=ch-'0';
    while(true){
        ch=getchar();
        if(ch<'0'||ch>'9') break;
        x=x*10+ch-'0';
    }return x;
}
int main()
{
    int n,m,maxn=0;n=read(),m=read();
    int sum[n+1][m+1];
    for(int i=1;i<=n;i++)
        for(int j=1,x;j<=m;j++){
            x=read();
            //sum[][]记录前缀和 
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+x;
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            //k枚举正方形的边长 
            for(int k=1;k;k++){
                int x=i+k-1,y=j+k-1;
                //如果正方形的范围超出了矩阵,就结束这层循环 
                if(y<1||y>m||x<1||x>n) break;
                if(k*k==sum[x][y]-sum[i-1][y]-sum[x][j-1]+sum[i-1][j-1])
                    maxn=max(maxn,k);
            }
    printf("%d",maxn);
    return 0;
}

思路中关于前缀和的内容,本篇文章不做过多解释,想学习的的戳这里(此处给出的链接并不是本人的文章)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值