【NOI2017】无限棋盘

6 篇文章 0 订阅
4 篇文章 0 订阅

Description

无聊的小A在一个无限大的棋盘上玩游戏,这个棋盘由一个M*N的模板不停重复生成。例如,当模板为:
honi
hsin
时,我们会生成如下棋盘:
…honihonihonihoni…
…hsinhsinhsinhsin…
…honihonihonihoni…
…hsinhsinhsinhsin…
其中,该棋盘在任意一个方向都可以无限延伸。
现在小A在棋盘上随机挑选一个位置,又随机挑选一个方向(八个方向之一),并从该位置开始,沿着挑选的方向走K-1步,沿路记下每一个经过的字母(包括起点),得到一个长度为K的字符串。他重复并独立地执行该操作两次,得到两个长度为K的字符串,他现在想知道,这两个字符串相同的概率是多大?

Solution

比赛的时候对循环节的最长长度分析错误,然后就没有打。
其实正解并不需要找到循环节,可以采用二分的思想,把k拆开变成2的幂数的和,然后做类似RMQ的DP转移就可以了。因为要压缩空间,所以我们分开一个一个方向来做,设 fi,j,k 表示在(i,j)开始向某个方向延伸至 2k 的长度,就可以利用之前的DP值来转移。答案计算很明显是 2(8nm)2 ,把所有求出来的长度 k <script type="math/tex" id="MathJax-Element-442">k</script>的字符串放入一个答案数组中排序。字符串可以用它的哈希值表示,用C++的long long类型自然溢出就可以不用担心模数被卡。

Code

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define fo(i,a,b) for(i=a;i<=b;i++)
typedef long long ll;
const int N=505,M=505;
int F[8][2]={{-1,0},{1,0},{0,-1},{0,1},{-1,1},{1,1},{1,-1},{-1,-1}};
int n,m,o,l,i,j,k,x,y;
bool p[32];
char s[N][M];
ll ans[N*M*8],t,f[N][M][31],z,fz,fm,len,sum;
void fx(int i,int j,int k,int l){
    x=(i+F[k][0]*(1<<(l-1)))%n;x=(x-1+n)%n+1;
    y=(j+F[k][1]*(1<<(l-1)))%m;y=(y-1+m)%m+1;
}
void gcd(ll x,ll y){
    ll t;
    while(x%y){
        t=x%y,x=y,y=t;
    }
    fz=fz/y;fm=fm/y;
}
int main(){
    freopen("chessboard.in","r",stdin);
    freopen("chessboard.out","w",stdout);

    scanf("%d%d%d",&n,&m,&len);
    o=len;
    for(l=30;l>=0;l--) if((1<<l)<=o){
        p[l]=true;o-=(1<<l);
    }
    fo(i,1,n) scanf("%s",s[i]+1);
    fo(i,1,n) fo(j,1,m) f[i][j][0]=(s[i][j]-'a')+1;
    fo(k,0,7){
        z=37;
        fo(l,1,30){
            if((1<<l)>len) break;
            fo(i,1,n) fo(j,1,m){
                fx(i,j,k,l);
                f[i][j][l]=f[x][y][l-1]*z+f[i][j][l-1];
            }
            z=z*z;
        }
        fo(i,1,n) fo(j,1,m){
            t=0;x=i;y=j;z=37;
            fo(l,0,30){
                if(p[l]){
                    t=t*z+f[x][y][l];
                    fx(x,y,k,l+1);
                }
                z=z*z;
            }
            ans[++sum]=t;
        }
    }
    sort(ans+1,ans+sum+1);
    ans[0]=len=fz=0;fm=sum*sum;
    fo(i,1,sum) if(ans[i]!=ans[i-1]) 
        fz+=len*len,len=1;
        else len++;
    fz+=len*len;
    gcd(fz,fm);
    printf("%lld/%lld",fz,fm);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值