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(8∗n∗m)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);
}