一上来脑残,以为把边界包一圈 _ ,然后纵向最近的两个 _ 的距离就是所求的r,横向最近距离就是c。下面先给出一个反例。
n=m=7.
XXXXXXX
XXXXXXX
XX_ XXXX
XXXX_ XX
XXX_ _ XX
XXXXXXX
XXXXXXX
在上例中,横向、纵向最近的两个_的距离都是2,但2*2显然是不对的,答案应该是1*2.
我们首先观察到一个性质,就是其实我们只需要考虑覆盖与 _ 直接四联通相邻的X就可以了。因为假如用一个矩形覆盖了所有四联通相邻的X之后,还有一个X没有被覆盖,那么我们考虑它右边一路X过去知道边界的那个X所在的矩形,如果它能平移到这个X的话,直接平移过来就可以了;如果不行的话就是因为它被 _ 挡住了,那么这个 _ 在这个X的方向的边界X又会被一个矩形覆盖,那么就把这个矩形平移过来就可以了;如果依然不行的话就重复如此。。所以这个X是必然是可以被覆盖的。
就是说我们只需要完全覆盖边界的X,内部的X便都可以被覆盖。
而因为我们要求矩形面积最小,所以我们可以枚举r,看c最大能到多少;枚举c也是类似的。
那么我们考虑在 _ 下面的X,我们可以枚举覆盖它的矩形的r(听说这好像就是传说中的悬线法。。),然后就能得到对于每一个r,c可取的最大值;对于在 _ 上面的也同理;左面的就枚举覆盖它的c;而右面的其实就不需要考虑了,因为考虑上面性质的证明过程,可以发现其实这个证明不止适用于内部,也适用于考虑三个方向后的第四个方向的X。所以我们只需要做三遍就可以了(其实主要是为了卡常数。。)。
最后我们可以得到每一个r,c可取的最大值和每一个c,r可取的最大值;将二者合并然后找其中面积最大的就可以了。
时间复杂度是
O(n2)
。
这道题常数有点卡。。用二维数组的时候要注意一下枚举顺序。。
代码:
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
char * cp=(char *)malloc(10000000);
const int N=2500+5;
bool a[N][N],tmp[N][N];
int dis[N][N],llen[N][N],rlen[N][N],r[N];
int cmax[N],rmax[N];
void work(int n,int m,bool a[N][N],int cmax[N])
{
/*puts("-----------");
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)putchar(a[i][j]?'X':'_');
puts("");
}*/
int r0=n;
r[m+1]=0;
for(int j=m;j;--j)llen[0][j]=rlen[0][j]=m;
for(int i=1;i<=n;++i)
{
for(int j=m;j;--j)r[j]=a[i][j]?r[j+1]+1:0;
int l=0;
for(int j=1;j<=m;++j)
if(a[i][j])
{
++l;
dis[i][j]=dis[i-1][j]+1;
llen[i][j]=min(llen[i-1][j],l),rlen[i][j]=min(rlen[i-1][j],r[j]);
cmax[dis[i][j]]=min(cmax[dis[i][j]],llen[i][j]+rlen[i][j]-1);
if(!a[i+1][j])r0=min(r0,dis[i][j]);
}
else dis[i][j]=l=0,llen[i][j]=rlen[i][j]=m;
}
for(int i=r0+1;i<=n;++i)cmax[i]=0;
//for(int i=1;i<=n;++i)printf("cmax[%d]=%d\n",i,cmax[i]);
}
int main()
{
freopen("bzoj3736.in","r",stdin);
//freopen("bzoj3736.out","w",stdout);
int n,m;
scanf("%d%d",&n,&m);
fread(cp,1,10000000,stdin);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
while(*cp!='_'&&*cp!='X')++cp;
a[i][j]=*cp++=='X';
}
for(int i=n;i;--i)cmax[i]=m;
work(n,m,a,cmax);
for(int j=m;j;--j)
for(int i=n>>1;i;--i)
swap(a[i][j],a[n-i+1][j]);
//work(n,m,a,cmax);
for(int i=m;i;--i)rmax[i]=n;
for(int i=n;i;--i)
for(int j=m;j;--j)
tmp[j][i]=a[i][j];
work(m,n,tmp,rmax);
for(int i=n,j=1;j<=m;++j)
for(;i>rmax[j];--i)
cmax[i]=min(cmax[i],j-1);
int ansr,ansarea=0;
for(int r=1;r<=n;++r)
if(r*cmax[r]>ansarea){
ansarea=r*cmax[r];
ansr=r;
}
printf("%d %d\n",ansr,cmax[ansr]);
}
总结:
①用二维数组的时候一定要想好枚举顺序,改变一下顺序带来的常数差距是很大的。