[JZOJ5045]无限棋盘

这是一篇关于在线性代数竞赛(OI)问题的博客,讨论了如何解决‘无限棋盘’的挑战。题目要求在无限延伸的棋盘上随机行走并计算路径相同字符串的概率。博主提出使用哈希和倍增技术来计算不同方向的哈希值,通过排序和哈希碰撞概率来求解,整体算法的时间复杂度为O(nmlogK)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给定一个nm列的字符串模板,让这个模板在平面内无限延伸形成棋盘(就是每一份模板的上下左右都是这个模板)。
现在你要在这个棋盘上随机挑选一个位置,又随机挑选一个方向(八个方向之一),并从该位置开始,沿着挑选的方向走K1步,沿路记下每一个经过的字母(包括起点),得到一长度为K的字符串。试求出重复并独立执行该操作两次,得到的两个长度为K的字符串相同的概率。
以最简分数形式输出答案。

1n,m500,2K109


题目分析

基本思路是弄出n×m个格子所有方向的哈希值都弄出来,然后排序计算。
但是怎么搞出那个哈希值才能既保证复杂度又节省代码呢?
倍增!处理2i长度的哈希值合并起来。
时间复杂度O(nmlogK)


代码实现

#include <algorithm>
#include <iostream>
#include <utility>
#include <cstdio>

using namespace std;

typedef long long LL;

LL sqr(LL x){return x*x;}

const int N=505;
const int M=505;
const int D=8;
const int MOD=199999279;
const int P1=89;
const int P2=461;

typedef pair<int,int> P;

#define mkp(a,b) make_pair(a,b)
#define ft first
#define sd second

P operator+(P a,P b){return mkp((a.ft+b.ft)%MOD,(a.sd+b.sd)%MOD);}
P operator-(P a,P b){return mkp((a.ft-b.ft+MOD)%MOD,(a.sd-b.sd+MOD)%MOD);}
P operator*(P a,P b){return mkp(1ll*a.ft*b.ft%MOD,1ll*a.sd*b.sd%MOD);}
P operator*(P x,int y){return x*mkp(y%MOD,y%MOD);}
P operator+(P x,int y){return x+mkp(y%MOD,y%MOD);}
P operator-(P x,int y){return x-mkp(y%MOD,y%MOD);}

P quick_power(P x,int y)
{
    P ret=mkp(1,1);
    for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;
    return ret;
}

LL gcd(LL x,LL y){return y?gcd(y,x%y):x;}

int dir[D][2]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
P f[2][N][M][D],g[N][M][D];
int tot,n,m,K;
char s[N][M];
P app[N*M*D];
P pri;
LL A,B;

void calc()
{
    pri=mkp(P1,P2);
    for (int i=0;i<n;++i)
        for (int j=0;j<m;++j)
            for (int d=0;d<D;++d)
                f[0][i][j][d]=mkp(s[i][j]-'a',s[i][j]-'a');
    for (int l=0,now=0,lst=1,sig=0;l<30;++l)
    {
        P tmp;int len=1<<l;
        if ((K>>l)&1)
        {
            tmp=quick_power(pri,sig);
            for (int i=0;i<n;++i)
                for (int j=0;j<m;++j)
                    for (int d=0;d<D;++d)
                    {
                        int x=((i+dir[d][0]*sig)%n+n)%n,y=((j+dir[d][1]*sig)%m+m)%m;
                        g[i][j][d]=g[i][j][d]+(f[now][x][y][d]*tmp);
                    }
            sig+=len;
        }
        tmp=quick_power(pri,len);
        swap(now,lst);
        for (int i=0;i<n;++i)
            for (int j=0;j<m;++j)
                for (int d=0;d<D;++d)
                {
                    int x=((i+dir[d][0]*len)%n+n)%n,y=((j+dir[d][1]*len)%m+m)%m;
                    f[now][i][j][d]=f[lst][i][j][d]+(f[lst][x][y][d]*tmp);
                }
    }
    for (int i=0;i<n;++i)
        for (int j=0;j<m;++j)
            for (int d=0;d<D;++d)
                app[++tot]=g[i][j][d];
    sort(app+1,app+1+tot);
    for (int i=1,j;i<=tot;i=j)
    {
        for (j=i+1;j<=tot&&app[i]==app[j];++j);
        A+=sqr(j-i);
    }
}

int main()
{
    freopen("chessboard.in","r",stdin),freopen("chessboard.out","w",stdout);
    scanf("%d%d%d",&n,&m,&K);
    for (int i=0;i<n;++i) scanf("%s",s[i]);
    calc(),B=sqr(1ll*n*m*D);
    LL C=gcd(A,B);A/=C,B/=C;
    printf("%lld/%lld\n",A,B);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值