题解 P1387 【最大正方形】

· · 题解

update: 前缀和加二分的解释及程序

水题水题水题

暴力出奇迹

Ps: 有评论区问能不能用二分做 答案是 可以 的 代码解释请看下

本篇题解介绍纯暴力方法。不会炸,还挺快(不会DP直说嘛)

【题外话】

考场上不要盲目地追求正解!先全部写完暴力(可以和正解对拍),再慢慢研究正解。不能在一题上耗费过多时间,其实有时候你辛辛苦苦写了个正解,某个地方写挂了,那还不如人家乱打的暴力.

【思路】

枚举每一个点作为所选正方形的左上角的点,然后枚举正方形边长,逐一判断。

【优化】

Code:

#include <bits/stdc++.h>
#define re register int
using namespace std;
bool f[202][202]={0},p;
int n,m,i,j,k,x,y,ans=0;
int main()
{
  scanf("%d%d",&n,&m);
  for (re i=0; i<n; i++)
    for (re j=0; j<m; j++)
      scanf("%d",&f[i][j]);          //读入不解释
  for (re i=0; i<n; i++)
    for (re j=0; j<m; j++)
      for (re k=min(n,m); k>ans; k--)//优化1
        {
          p=1;
          for (re x=i; x<i+k; x++)   //逐个判断
            {
              for (re y=j; y<j+k; y++)
                if (!f[x][y])        //不是1的话
                  {
                    p=0; break;      //标记,跳出循环,优化2
                  }
              if (!p) break;         //不符合没必要接着做,优化2
            }
          if (p)                     //符合条件的话
            {
              ans=k; break;          //优化3
            }
        }
  printf("%d",ans);                  //输出
  return 0;
}

【一些思考】

假如n再大些,我们可以用前缀和,分为以下两种:

f[i+k][j+k] - f[i+k][j] - f[i][j+k] + f[i][j]

是否 < k^2 即可,小于则不满足条件。 如下:

由于重复所以要加上去,稍微想一下就出来了。

【二分优化】

因为不同边长是否符合条件具有单调性

意思就是,设答案为ans,那么

  1. 大于ans的肯定不符合条件(即有0),不然那个大于ans又符合条件的才是真正的答案(这是个假的ans),

  2. 小于ans的肯定都符合条件,因为边长为ans的正方形包含了边长<=ans的正方形,所以边长<ans的正方形符合条件(即全是1)是边长为ans的正方形符合条件的必要不充分条件。

    <x的不符合,x必然不符合, <x符合,x也有可能不符合(x=边长)

    综上,得到小于ans的都符合。

有了单调性后就可以二分优化啦!

二分边长k,用之前的判断方法判断,如果可行则增大k(没准可以更大), 否则减小k

这里给出上面O(n^3)的优化方案,时间复杂度优化成了O(n^2log(n))

#include <bits/stdc++.h>
using namespace std;
int n, m, ans = 0, x, f[205][205];
int main() {
    scanf("%d%d", &n, &m);
    for (int i=0; i<n; ++i)
        for (int j=0; j<m; ++j) {
            scanf("%d", &x);
            f[i][j]= f[i-1][j]+f[i][j-1]-f[i-1][j-1]+x;
        }
    for (int i=0; i<n; ++i)
        for (int j=0; j<m; ++j) {
            int l = 0, r = min(n,m);
            while (l<=r) {
                int mid = (l+r)>>1;
                if (i+mid>n || j+mid>m || f[i+mid][j+mid]-f[i+mid][j]-f[i][j+mid]+f[i][j] < mid*mid) r = mid-1;
                    else l = mid+1;
            }
            if (f[i+r][j+r]-f[i+r][j]-f[i][j+r]+f[i][j] == r*r) ans = max(ans, r);
        }
    cout << ans;
    return 0;
}

实测已AC,要注意的是二分后还要再判断一下,不然就只有40分

O(n^2)的话就去看楼下的dp吧。

我的虽然不是最优的,却是最好理解的!

如果您满意的话右上角的赞

加了\LaTeX , 更新 by 2018 - 11 - 17 20:00

修了\LaTeX 加了二分 , 更新 by 2019 - 9 - 11 6:47