Topcoder SRM 616 Div2 1000 TwoLLogo

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 1005
char 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;
}
内容概要:本文档详细介绍了Analog Devices公司生产的AD8436真均方根-直流(RMS-to-DC)转换器的技术细节及其应用场景。AD8436由三个独立模块构成:轨到轨FET输入放大器、高动态范围均方根计算内核和精密轨到轨输出放大器。该器件不仅体积小巧、功耗低,而且具有广泛的输入电压范围和快速响应特性。文档涵盖了AD8436的工作原理、配置选项、外部组件选择(如电容)、增益调节、单电源供电、电流互感器配置、接地故障检测、三相电源监测等方面的内容。此外,还特别强调了PCB设计注意事项和误差源分析,旨在帮助工程师更好地理解和应用这款高性能的RMS-DC转换器。 适合人群:从事模拟电路设计的专业工程师和技术人员,尤其是那些需要精确测量交流电信号均方根值的应用开发者。 使用场景及目标:①用于工业自动化、医疗设备、电力监控等领域,实现对交流电压或电流的精准测量;②适用于手持式数字万用表及其他便携式仪器仪表,提供高效的单电源解决方案;③在电流互感器配置中,用于检测微小的电流变化,保障电气安全;④应用于三相电力系统监控,优化建立时间和转换精度。 其他说明:为了确保最佳性能,文档推荐使用高质量的电容器件,并给出了详细的PCB布局指导。同时提醒用户关注电介质吸收和泄漏电流等因素对测量准确性的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值