Topcoder SRM 616 Div2 1000 TwoLLogo

来源:互联网 发布:英雄无敌3恐怖骑士知乎 编辑:程序博客网 时间:2024/06/06 03:52

Task:
给定一个nm的平面图,由’.’与’#’构成,现要在’.’处画出两个L形图案,要求两个L不能相交,求方案数,答案对P取模。(n<=1000,P=1000000007)
Solution:
一个L由四个数字坐标决定,因此最朴素的做法即是枚举两个L,复杂度为O(n8),期望得分为30分。

bool judge(int x1,int y1,int l1,int r1,int x,int y){    if(x==l1){        if(y>=y1&&y<=r1)return true;    }    if(y==y1){        if(x>=x1&&x<=l1)return true;    }    return false;}struct P30{    void solve(){        ll ans=0;        for(int x1=1;x1<=n;x1++)            for(int y1=1;y1<=m;y1++){                if(str[x1][y1]=='#')continue;                for(int l1=x1+1;l1<=n;l1++){                    if(str[l1][y1]=='#')break;                    for(int r1=y1+1;r1<=m;r1++){                        if(str[l1][r1]=='#')break;                        for(int x2=1;x2<=n;x2++)                            for(int y2=1;y2<=m;y2++){                                if(str[x2][y2]=='#'||judge(x1,y1,l1,r1,x2,y2))continue;                                for(int l2=x2+1;l2<=n;l2++){                                    if(str[l2][y2]=='#'||judge(x1,y1,l1,r1,l2,y2))break;                                    for(int r2=y2+1;r2<=m;r2++){                                        if(str[l2][r2]=='#'||judge(x1,y1,l1,r1,l2,r2))break;                                        ans++;                                    }                                }                            }                    }                }            }        cout<<(ans/2)%P<<endl;    }}P30;

首先,显然在枚举的过程中,对于L的两条边的长度是不必要在循环内检验的,可以直接用O(n2)预处理出来。
定义right[i][j]表示从(i,j)点向右拓展能够拓展的长度,up[i][j]表示从(i,j)点向上拓展能够拓展的长度。
因此我们枚举L的拐点坐标,会有以下三种情况:
情况
我们设第一个L的坐标为(x1,y1),第二个为(x2,y2)

  • 对于第一种,只有标红的线段是限定的,其余线段无关,因此
    tot=up[x1][y1]right[x1][y1]right[x2][y1]min(up[x2][y1],x2x11);
  • 对于第二种,所有线段都无限制,因此
    tot=up[x1][y1]up[x2][y2]right[x1][y1]right[x2][y2]
  • 对于第三种,第一个L向上的与第二个L向右的无关,而交点虚线部分的处理就比较麻烦了。我们可以分情况讨论:
    • 交点处给第一个Ltot=right[x1][y1]min(up[x2][y2],x2x11)
    • 交点处给第二个L,第二个L向上的边在红色段已经在前一种方案的统计中算过,因此:tot=(up[x2][y2]min(up[x2][y2],x2x11))min(right[x1][y1],y2y11)

于是就写出了O(n4)的代码,期望得分80分。

void Init(){    for(int j=1;j<=m;j++)        for(int i=1;i<=n;i++){            if(str[i][j]=='#')continue;            if(i==1||str[i-1][j]=='#')up[i][j]=0;            else up[i][j]=up[i-1][j]+1;        }    for(int i=1;i<=n;i++)        for(int j=m;j>=1;j--){            if(str[i][j]=='#')continue;            if(j==m||str[i][j+1]=='#')right[i][j]=0;            else right[i][j]=right[i][j+1]+1;        }}struct P80{    ll calc(int x1,int y1,int x2,int y2){//y1<y2        if(x1==x2)return 1LL*Up[x1][y1]*Up[x2][y2]*min(Right[x1][y1],y2-y1-1)*Right[x2][y2];        if(x1>x2)return 1LL*Up[x1][y1]*Up[x2][y2]*Right[x1][y1]*Right[x2][y2];        else{            int t=min(Up[x2][y2],x2-x1-1);            return 1LL*Up[x1][y1]*Right[x2][y2]*(1LL*Right[x1][y1]*t+1LL*(Up[x2][y2]-t)*min(Right[x1][y1],y2-y1-1));        }    }    void solve(){        ll ans=0;        for(int i=1;i<=n;i++)            for(int j=1;j<=m;j++){                if(str[i][j]=='#')continue;                for(int k=j+1;k<=m;k++)                    if(str[i][k]=='#')break;                    else Right[i][j]++;            }        for(int i=1;i<=n;i++)            for(int j=1;j<=m;j++){                if(str[i][j]=='#')continue;                for(int k=i-1;k>=1;k--)                    if(str[k][j]=='#')break;                    else Up[i][j]++;            }        for(int y1=1;y1<=m;y1++)            for(int x1=1;x1<=n;x1++){                if(str[x1][y1]=='#')continue;                //y2==y1 judge                for(int x2=x1+1;x2<=n;x2++){                    if(str[x2][y1]=='#')continue;                    ans+=1LL*Up[x1][y1]*Right[x1][y1]*Right[x2][y1]*min(Up[x2][y1],x2-x1-1);                }                for(int y2=y1+1;y2<=m;y2++)                    for(int x2=1;x2<=n;x2++){                        if(str[x2][y2]=='#')continue;                        ans+=calc(x1,y1,x2,y2);                    }            }        cout<<ans%P<<endl;    }}P80;

其实标程的复杂度就是O(n4),然而 KyleYoung 大神写出了O(n2)的解法…Orz
凭借“正难则反”的指导思想,我们可以先算出总方案数,再减去不符合的方案数。
同样的,对于不符合的方案数,也是三种情况。我们可以选择一个特殊点来枚举,即图中两个L的交叉点。设此点坐标为(x,y)
首先我们与处理出left数组与down数组。left[i][j]表示(i,j)左边各点向上延伸的方案数之和,down[i][j]表示(i,j)下边向右延伸的方案数之和。

left[i][j]=left[i][j1]+up[i][j1]
down[i][j]=down[i+1][j]+right[i+1][j]

情况
- 对于第一种,第一个L的右边范围为与第二个L的向上延伸范围为红色区域,其余线段无关:tot=(up[i][j]+1)down[i][j](right[i][j]+1)left[i][j]
- 对于第二种:tot=right[i][j]up[i][j]left[i][j](right[i][j]+1)
- 对于第三种:tot=right[i][j]up[i][j]down[i][j](up[i][j]+1)
于是就解决了这道题,复杂度O(n2)

#include<stdio.h>#define P 1000000007#define M 1005char str[M][M];int up[M][M],down[M][M],left[M][M],right[M][M];int n,m;void Init(){    for(int j=1;j<=m;j++)        for(int i=1;i<=n;i++){            if(str[i][j]=='#')continue;            if(i==1||str[i-1][j]=='#')up[i][j]=0;            else up[i][j]=up[i-1][j]+1;        }    for(int i=1;i<=n;i++)        for(int j=m;j>=1;j--){            if(str[i][j]=='#')continue;            if(j==m||str[i][j+1]=='#')right[i][j]=0;            else right[i][j]=right[i][j+1]+1;        }    for(int i=1;i<=n;i++)        for(int j=2;j<=m;j++)            if(str[i][j]!='#')left[i][j]=left[i][j-1]+up[i][j-1];    for(int j=1;j<=m;j++)        for(int i=n-1;i>=2;i--)            if(str[i][j]!='#')down[i][j]=down[i+1][j]+right[i+1][j];}int total(){    int sum=0,res=0;    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)            if(str[i][j]!='#'){                int t=1LL*up[i][j]*right[i][j]%P;                res=(res+1LL*t*sum)%P;                sum=(sum+t)%P;            }    return res;}int calc1(){    int res=0;    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)            if(str[i][j]!='#')res=(res+1LL*(up[i][j]+1)*down[i][j]%P*(right[i][j]+1)%P*left[i][j]%P)%P;    return res;}int calc2(){    int res=0;    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)//横向             if(str[i][j]!='#')                res=(res+1LL*right[i][j]*up[i][j]%P*left[i][j]%P*(right[i][j]+1)%P)%P;    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)//竖直             if(str[i][j]!='#')res=(res+1LL*right[i][j]*up[i][j]%P*down[i][j]%P*(up[i][j]+1)%P)%P;    return res;}int doit(int x){    return (x%P+P)%P;}int main(){    scanf("%d %d",&n,&m);    for(int i=1;i<=n;i++)        scanf("%s",str[i]+1);    Init();    printf("%d\n",doit(doit(total()-calc1())-calc2()));    return 0;}
0 0