创意吃鱼法

Posted by Panda2134's Blog on August 19, 2017

链接:Luogu-P1736

题目大意

有一个$n \times m$的矩阵,要在里面寻找一个尽可能大的正方形,使得这个正方形某条对角线上都是1,其他地方都是0。求这个正方形对角线长。

思路

注意到求的是正方形,所以是裸的二维平面DP。对此类问题,画出图像,定义状态为以某个位置为右下角的最大合法正方形;再把“障碍方格”绕着可能影响转移的位置移动即可找出转移。

如本题,先考虑主对角线的情况。定义状态$f(i,j)$ : 以$(i,j)$ 为右下角的最大合法正方形。 如果$(i,j)$本身不是1,$f(i,j)=0$。如果它本身是1,情况如图。对于图中所有标有*的方块,如果都是0,显然就直接有$f(i,j)=f(i-1,j-1)+1$。如果其中存在一个1,那么它就破坏了本来可能形成的,边长为 $f(i-1,j-1)+1$ 的新正方形。换句话说,它就是个“障碍方格”,而* 位置就是可能影响转移的位置。 fish 把它在所有的*中移动,我们发现,只需要考虑离当前$(i,j)$在向左和向上方向最近的一个 1 的位置即可,而这个可以递推求得。

综合考虑三者可以得到: $f(i,j)=\min\{ a,b, f(i-1,j-1)+1 \}$

其中$a,b$为从$(i,j)$出发向左向上分别至少走多少步可以到达一个1(不考虑本身)。

至于副对角线,可以重新定义一个类似的状态,也可以把矩阵“翻折”一下,把原矩阵的第$j$列放到新矩阵的第$m-j+1$列,这样就转化为关于主对角线的问题了。

时间复杂度是$O(nm)$。

代码

注意递推的边界值,尤其是如果你针对副对角线重新定义了状态的话。

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#define CLEAR(x) memset(x,0,sizeof(x))
using namespace std;
const int MAXN = 2500;
int N,M,Ans,W[MAXN+2][MAXN+2],opt[MAXN+2][MAXN+2],f[MAXN+2][MAXN+2],g[MAXN+2][MAXN+2];
//对点(i,j),f是往左最近的一个'1',g是往上最近的一个'1'。不包括(i,j)本身。 
inline int readint(){
    int f=1,r=0;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){r=r*10+c-'0';c=getchar();}
    return f*r;
}
inline void solve(){
    for(int i=1;i<=N;i++)
        for(int j=1;j<=M;j++) {
            f[i][j]=f[i][j-1]+1;
            g[i][j]=g[i-1][j]+1;
            if(W[i][j]){
                opt[i][j]=min(min(f[i][j],g[i][j]),opt[i-1][j-1]+1);
                f[i][j]=g[i][j]=0;
            }
            Ans=max(Ans,opt[i][j]); 
        } 
}
int main(){
    N=readint(); M=readint();
    for(int i=1;i<=N;i++)
        for(int j=1;j<=M;j++)
            W[i][j]=readint();
    solve();
    for(int j=1;j<=M/2;j++)
        for(int i=1;i<=N;i++)
            swap(W[i][j],W[i][M-j+1]);
    CLEAR(opt);CLEAR(f);CLEAR(g);
    solve();
    printf("%d\n",Ans);
}