[CF677E] Vanya and Balloons

题目大意

给你一个只有0,1,2的n*n的矩阵,你现在可以选择一个叉叉或者十字,把里面的数全部取掉,贡献为取掉的数的乘积。
具体的,从某一点(i,j)出发,选择一个长度c:
你可以这样取(1为取,0为不取)
00100
00100
11111
00100
00100
或者这样取
10001
01010
00100
01010
10001
n<=1000

分析

很简单的思路,弄两种,共4个前缀和数组,维护两个对角线的前缀和,还有一行一列的前缀和,然后枚举以哪个点作为中心。然后怎么确定这个点怎么取呢?我们知道如果要取,在取不到0时,肯定c越大越好;取到0就没有意义了。所以两种取法,分别二分长度,看看最大的c是多少,用log比较大小,更新答案即可。
其实原来我还怕精度不够,想直接弄double比较,然而精度其实差不多,而且double只有301位,即 21000 ,如果全是3的话早爆了。
这道题比较麻烦的地方是对角线前缀和数组,要码得快必须要先规定好表示法,然后再打,这样才能思路清晰,不至于打得慢。
其实这题最好用直角坐标系,然后直接设数组bx[i][j]表示y=±x+i这条直线,会比较好做。
然而我没有转成坐标系,直接bx[i][j]表示起点为(i,1),到(i+j-1,j)的前缀和。另一条对角线类似,以(i,n)为起点,为了储存地址都是正数,还要整体移n。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=2005;
const ll mo=1e9+7;
struct rec
{
    int c[4];
}ax[N][N],ay[N][N],bx[N][N],by[N][N],ret,ans;
int i,j,k,n,m,l,r,mid,a[N][N],xx,x,y,mn,z,prt;
db two[N];
char ch;
rec ins(rec a,int z)
{
    a.c[z]++;
    return a;
}
bool operator <(rec a,rec b)
{
    if (a.c[0]) return 1;
    if (b.c[0]) return 0;
    db aa,bb;
    aa=log(2)*(db)a.c[2]+log(3)*(db)a.c[3];
    bb=log(2)*(db)b.c[2]+log(3)*(db)b.c[3];
    return aa<bb;
}
rec operator +(rec a,rec b)
{
    a.c[0]=a.c[0]+b.c[0];
    a.c[1]=a.c[1]+b.c[1];
    a.c[2]=a.c[2]+b.c[2];
    a.c[3]=a.c[3]+b.c[3];
    return a;
}
rec operator -(rec a,rec b)
{
    a.c[0]=a.c[0]-b.c[0];
    a.c[1]=a.c[1]-b.c[1];
    a.c[2]=a.c[2]-b.c[2];
    a.c[3]=a.c[3]-b.c[3];
    return a;
}
void get1(int x)
{
    ret=ax[i][j+x]-ax[i][j-x-1]+ay[j][i+x]-ay[j][i-x-1];
}
void get2(int x)
{
    int y=n-j+1;
    ret=bx[i-j+n+1][j+x]-bx[i-j+n+1][j-x-1]+by[i+j][y+x]-by[i+j][y-x-1];
}
int main()
{
//  freopen("677.in","r",stdin);
    scanf("%d\n",&n);
    fo(i,1,n) 
    {
        fo(j,1,n)
        {
            scanf("%c",&ch);
            a[i][j]=ch-'0';
        }
        scanf("\n");
    }
    ans.c[0]=1;
    fo(i,1,n)
        fo(j,1,n)
        {
            ax[i][j]=ins(ax[i][j-1],a[i][j]);
            ay[j][i]=ins(ay[j][i-1],a[i][j]);
        }
    fo(xx,-n+2,n)
    {
        i=xx+n;
        x=xx;
        z=n;
        fo(y,1,n)
        {
            if (x>0)
            {
                bx[i][y]=ins(bx[i][y-1],a[x][y]);
                by[i][y]=ins(by[i][y-1],a[x][z]);
            }
            x++;
            z--;
        }
    }
    fo(i,1,n)
        fo(j,1,n)
        if (a[i][j])
        {
            // non-rotated
            mn=min(min(i-1,n-i),min(j-1,n-j));
            l=0;
            r=mn;
            while (l<r)
            {
                mid=(l+r+1)/2;
                get1(mid);
                if (ret.c[0]==0) l=mid;
                else r=mid-1;
            }
            get1(l);
            ret.c[a[i][j]]--;
            if (ans<ret)
                ans=ret;
            //rotated
            l=0;
            r=mn;
            while (l<r)
            {
                mid=(l+r+1)/2;
                get2(mid);
                if (ret.c[0]==0) l=mid;
                else r=mid-1;
            }
            get2(l);
            ret.c[a[i][j]]--;
            if (ans<ret)
                ans=ret;
        }
        prt=1;
        fo(i,0,3)
            fo(j,1,ans.c[i])
                prt=(ll)prt*i%mo;
        printf("%d\n",prt);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值