2016 Multi-University Training Contest 1 C Game(hdu 5725)

题目链接

题目大意

棋盘上有一些守卫,每个守卫的攻击范围是他周围的八个格子,以及他所在的行和列。
已知现在棋盘上没有两个守卫可以相互攻击到。
求棋盘上所有可行路径(不经过守卫)的平均长度。

题解

题解说的挺简单的。
这题一点都不简单。
首先要yy出一个结论:
从题目这个奇怪的条件(守卫攻击范围大,却只有经过时才会触发)可以发现,守卫的分布是比较稀疏的。
所以猜想任意一条路径最多只可能被一个守卫影响。
这个模拟几组数据就会有感觉,但具体不知道如何证明。
于是可以先算出所有路径的和,然后在把必须经过一个守卫的路径条数算出来,乘二加入答案。
现在的问题分成了两个:
1.如何求所有一开始的路径和。
2.如何求必须经过一个守卫的路径条数。
第一个还是相对好解决的,可以利用二维前缀和。
可以发现,如果一个方块B在当前方块A的左上方,那么B对答案的贡献是两个负的,A是两个正的,即 dis=xaxb+yayb
如果B在A 的左下方,那么 dis=xaxb+ybya
所有的情况都可以被这两种情况包含,但是要注意同一行的情况,不可以重复算。
然后考虑如何解决第二个问题。
首先可以想到的是如果两个格子在同一行(列),并且它们之间有一个守卫,那么它们一定要经过守卫。
但是这还远远不够。
比如这组数据:
7 7
######G
###G###
#G#####
####G##
##G####
#####G#
G######
(1,4)到(6,3)就要经过一个守卫。
行与列相似,这里只考虑列。
不仅是相邻两列,不同的列之间也有可能。
比如(1,4)到(7,6),也必须要经过一个守卫。
所以可以猜想,如果守卫的位置是单调向下的,那么就可以一直往后面更新,路径条数就是当前列的守卫以上的空格数*对应列的守卫以下的空格数。
比如,第四列可以一直更新到第六列。第六列对答案的贡献是1*5+1*3+1*1=9
单调向上也与单调向下类似,只是左右反一下罢了。
这组数据答案是4.7846。
代码写的很丑,跑得极慢。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M=1005;
typedef long long ll;
char mp[M][M];
ll sum[2][M][M];
int tot[2][M][M],tmp[M],presum[4][M];
void solve(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",mp[i]+1);
    for(int i=1;i<=n;i++)
        presum[3][i]=m+1,presum[1][i]=0;
    for(int i=1;i<=m;i++)
        presum[2][i]=0,presum[0][i]=n+1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[i][j]=='G'){
                presum[3][i]=min(presum[3][i],j);
                presum[1][i]=max(presum[1][i],j);
                presum[0][j]=min(presum[0][j],i);
                presum[2][j]=max(presum[2][j],i);
            }
    for(int i=1;i<=n;i++){
        presum[3][i]--;
        presum[1][i]=m-presum[1][i];
    }
    for(int i=1;i<=m;i++){
        presum[0][i]--;
        presum[2][i]=n-presum[2][i];
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(presum[3][j-1]<presum[3][j]&&presum[3][i]!=m&&presum[1][j]!=m){
                ans+=2*presum[3][i]*presum[1][j];
            }else break;
        }
        for(int j=i+1;j<=n;j++){
            if(presum[3][j-1]>presum[3][j]&&presum[3][j]!=m&&presum[1][i]!=m){
                ans+=2*presum[1][i]*presum[3][j];
            }else break;
        }
    }
    for(int i=1;i<=m;i++){
        for(int j=i+1;j<=m;j++){
            if(presum[0][j-1]>presum[0][j]&&presum[0][i]!=n&&presum[2][j]!=n){
                ans+=2*presum[2][i]*presum[0][j];
            }else break;
        }
        for(int j=i+1;j<=m;j++){
            if(presum[0][j-1]<presum[0][j]&&presum[2][j]!=n&&presum[0][i]!=n){
                ans+=2*presum[0][i]*presum[2][j];
            }else break;
        }
    }
    int cnt=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            tot[0][i][j]=tot[0][i-1][j]+tot[0][i][j-1]-tot[0][i-1][j-1]+(mp[i][j]!='G');
            sum[0][i][j]=sum[0][i-1][j]+sum[0][i][j-1]-sum[0][i-1][j-1]-(i+j)*(mp[i][j]!='G');
        }
    for(int i=n;i>=1;i--)
        for(int j=1;j<=m;j++){
            tot[1][i][j]=tot[1][i][j-1]+tot[1][i+1][j]-tot[1][i+1][j-1]+(mp[i][j]!='G');
            sum[1][i][j]=sum[1][i][j-1]+sum[1][i+1][j]-sum[1][i+1][j-1]+(i-j)*(mp[i][j]!='G');
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(mp[i][j]=='G') continue;
            ans+=1LL*(i+j)*tot[0][i][j]+sum[0][i][j];
            ans+=1LL*(-i+j)*tot[1][i+1][j-1]+sum[1][i+1][j-1];
        }
    tmp[m+1]=0;
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1;j--)
            tmp[j]=tmp[j+1]+(mp[i][j]!='G');
        for(int j=1;j<=m;j++)
            if(mp[i][j]=='G')
                ans+=2LL*(tot[0][i][j]-tot[0][i][j-1])*(tot[1][i][j]-tot[1][i][j-1])+2LL*(tot[0][i][j]-tot[0][i-1][j])*tmp[j];
            else cnt++;
    }
    printf("%.4lf\n",(double)ans*2/cnt/cnt);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<2;k++)
                tot[k][i][j]=sum[k][i][j]=0;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}
/*
4
3 3
##G
G##
###
2 2
##
G#
3 3
##G
G##
###
7 7
######G
###G###
#G#####
####G##
##G####
#####G#
G######

1.7959
0.8889
1.7959
4.7846
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值