[Pa2013]Karty 解题报告

一上来脑残,以为把边界包一圈 _ ,然后纵向最近的两个 _ 的距离就是所求的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]);
}

总结:
①用二维数组的时候一定要想好枚举顺序,改变一下顺序带来的常数差距是很大的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值