https://www.luogu.org/problemnew/show/P1387

开始自己想的:设f(i,j):以(i,j)为左上角的包含(i,j)的最大子正方形大小,则f(i,j)取决于:设t=f(i+1,j+1),(i,j)右以及下方的t个元素最多连续几个1。由于只有01,所以用前缀和+二分可以o(logn)知道有几个连续的1,二分很麻烦写,不写的话用了前缀和也没什么用。

#include<bits/stdc++.h>
using namespace std;

int n,m,maxn;
int a[105][105],d[105][105];
int row[105][105],col[105][105];

int main()
{
//	freopen("input.in","r",stdin);
	cin>>n>>m;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>a[i][j];
	
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
	{
		row[i][j]=row[i][j-1]+a[i][j];
		col[i][j]=col[i-1][j]+a[i][j];
	}
	
	for(int i=n;i>=1;i--)
	    for(int j=m;j>=1;j--)
	    {
	    	int t=d[i+1][j+1];
	    	if(row[i][j+t]-row[i][j]==t&&col[i+t][j]-col[i][j]==t&&a[i][j])d[i][j]=t+1;
	    	else if(a[i][j])
			{
				int k1=i+1,k2=j+1;
				while(a[k1][j])k1++;
				while(a[i][k2])k2++;
				d[i][j]=min(k1-i,k2-j);
			}
	    	else d[i][j]=0;
		}
    
    for(int i=n;i>=1;i--)
	    for(int j=m;j>=1;j--)
	       maxn=max(maxn,d[i][j]);
		    
	cout<<maxn<<endl;
	return 0;
}

看题解,原来状态转移方程这么直接好写。状态表示一样,转移方程

if(a[i][j]==1)    f[i][j]=min{f[i][j+1],f[i+1][j],f[i+1][j+1]}+1;    else f[i][j]=0;

理解:f(i,j)主要来源于f(i+1,j+1),但是有可能本行或者本列不全为1,带上min{f[i][j+1],f[i+1][j]}就可以限制到这个条件了,因为f[i][j+1],f[i+1][j]与f(i+1,j+1)重叠的部分全是1,只会受本行/列的影响。画图理解。