题意
给定一个n*m的01矩阵,寻找最大的,相邻值不相同的子矩阵与子方阵。
\(n,m\leq 2,000\)
分析
朴素的做法是枚举两个点来确定矩阵,再暴力判断能不能满足条件。
复杂度\(O(n^4)\)爆炸。考虑优化:判断能不能满足条件时,存在很多重复判断,可以预处理。
但是,怎样进行预处理呢?这就需要我们改变枚举方式,简化预处理的步骤。
悬线法
今年应该不考
是一种DP(?)算法,常用于求满足条件的矩阵。
如果将一条线当做1*k的矩阵看待,则我们枚举每一条满足条件的线,并尝试将其向左右拓展构造矩阵。
那么只要在左右拓展长度中分别取min,就是所求答案。这种方法的复杂度是多少?
每个点如果和上方不同,就可以接到上面的线中。所以枚举每个点,就可以递推求出悬线up
\[up_{i,j}=up_{i-1,j}+(b_{i,j}\not= b_{i,j})\]
其中b是原始矩阵,初始值是1。复杂度\(O(n^2)\)
左右端拓展的对象是单个点,所以对于每个点都预处理出能向左右拓展到的位置。用递推求出。
\[\begin{cases}b_{i,j}\not=b_{i,j-1}\quad left_{i,j}=j\\b_{i,j}=b_{i,j-1}\quad left_{i,j}=left_{i,j-1}\end{cases}\]
\[\begin{cases}b_{i,j}\not=b_{i,j+1}\quad right_{i,j}=j\\b_{i,j}=b_{i,j+1}\quad right_{i,j}=right_{i,j+1}\end{cases}\]
复杂度仍然\(O(n^2)\)
2k可过。
正确性
我们需要证明的是,悬线法可以枚举到所有矩形。
假设我们要在这一团满足条件的联通块中找矩形。(丑,真TM丑)
那么它会不会取蓝色呢?不会,很明显绿色比他更优。有一个性质:矩形边缘必然紧贴联通块边缘。
所以对于每条悬线,它取到的矩阵是尽量大的。
(其实绿色也不满足,因为左右端没有取完整。)
画图宝才,luogu捡到鬼了
由刚才的性质得出,这个矩形所在的联通块即便边缘再粗糙,总是会有一条悬线的上端和矩形的上端重合。
所以,只要每条悬线都枚举到,每个可能是答案的矩形就会枚举到,而且会让它们尽量大。这题就解决了。
代码
别看了 抄的
#include<cstdio>//iostream库有left函数,会冲突
#include<algorithm>
using namespace std;
const int MAXN=2005;
int n,m;
bool b[MAXN][MAXN];
int left[MAXN][MAXN],right[MAXN][MAXN],up[MAXN][MAXN];
int sq,rect,temp;
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
left[i][j]=right[i][j]=j,up[i][j]=1,scanf("%d",&b[i][j]);
//输入、初始化
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
if(b[i][j]!=b[i][j-1])
left[i][j]=left[i][j-1];
for(int i=1;i<=n;i++)
for(int j=m-1;j>=1;j--)
if(b[i][j]!=b[i][j+1])
right[i][j]=right[i][j+1];
//递推预处理,注意范围和顺序
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(i>1&&b[i-1][j]!=b[i][j])
{
up[i][j]=up[i-1][j]+1;
left[i][j]=max(left[i][j],left[i-1][j]);
right[i][j]=min(right[i][j],right[i-1][j]);
//由于是矩形,left和right不能越界,要取一遍最小值
//另外看清楚存储的是位置不是距离,所以左端取距离更短相当于位置更靠后,下标更大。
}
temp=(right[i][j]-left[i][j]+1);
rect=max(rect,up[i][j]*temp);
sq=max(sq,min(up[i][j],temp)*min(up[i][j],temp));
//正方形同理。
}
printf("%d\n%d\n",sq,rect);
return 0;
}
最后,祝大家身体健康,再见。