洛谷P1387 最大正方形题解
我这里讲两种方法。
题意:在一个n*m的只包含0和1的矩阵里找出一个不包含0的最大正方形,输出最大边长。
数据范围:1<=n,m<=100。
题目链接:https://www.luogu.org/problemnew/show/P1387
DP方程是不可能推出来的,这辈子都不可能。这个数据范围,用DP太费脑了吧,那我们就用二维前缀和。
二维前缀和做法:
时间复杂度O(n*m*min(n,m))
比较简单,代码自行理解吧。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int n,m,a[105][105],sum[105][105],f[105][105][105],ans;//枚举起点 边长 int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ scanf("%d",&a[i][j]); sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j]; } for(int k=1;k<=min(n,m);k++)//边长 for(int i=k;i<=n;i++)//右端点 for(int j=k;j<=m;j++){ int pd1=sum[i][j]-sum[i-k][j]-sum[i][j-k]+sum[i-k][j-k]; int pd2=i*j-(i-k)*j-i*(j-k)+(i-k)*(j-k); if(pd1==pd2) ans=max(k,ans); } printf("%d",ans); return 0; }
但是如果n,m的数据范围变大怎么办,我们还得了解时间复杂度更优的DP做法。
DP做法:
F【i】【j】表示以(i,j)为右端点的最大正方形的边长。
当A【I】【J】为1的时候才更新F【I】【J】。
状态转移方程: f[i][j]=min(f[i-1][j],min(f[i][j-1],f[i-1][j-1]))+1;
为什么取两次min呢?
状态是由这三点拓展来的,更新之后必然会覆盖这三点,如果要取MAX的话,实际上以这三点中的某一个点为右端点的最大正方形边长是无法满足要求的,意思是,你需要一个让这三个点可以拓展到的正方形。
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #define ll long long int using namespace std; int n,m,a[105][105],f[105][105],ans; int main() { scanf("%d%d",&n,&m); for(register int i=1;i<=n;++i) for(register int j=1;j<=m;++j) scanf("%d",&a[i][j]); for(register int i=1;i<=n;++i) for(register int j=1;j<=m;++j) if(a[i][j]){ f[i][j]=min(f[i-1][j],min(f[i][j-1],f[i-1][j-1]))+1; ans=max(ans,f[i][j]); } printf("%d",ans); return 0; }
实际上:DP做法的时间复杂度还可以再优,可以边读入边更新,毕竟某个点更新的前提就是状态转移方程上的三个点都被输入就行了,自行理解吧!