【BZOJ4820】[SDOI2017]硬币游戏(高斯消元)

SDOI2017硬币游戏解析
本文深入解析了SDOI2017竞赛中的硬币游戏问题,通过构建AC自动机并运用高斯消元算法计算概率,解决大规模点数下字符串匹配的概率计算难题。

【BZOJ4820】[SDOI2017]硬币游戏(高斯消元)

题面

BZOJ
洛谷

题解

第一眼的感觉就是构\(AC\)自动机之后直接高斯消元算概率,这样子似乎就是\(BZOJ1444\)了。然而点数太多了,三方的消元没法做。
考虑如何优化点数,首先我们的所有点可以分为两种,一种是终止节点,另外一种则不是。
既然现在要某一个串出现,因此我们唯一需要考虑的是到达终止节点的情况。设\(f_i\)表示到达第\(i\)个串的终止位置,并且没有到达过其他终止节点的概率,也就是第\(i\)个串的答案。设\(f_0\)表示没有到达任何一个串终止位置的概率。
那么显然的,要到达当前位置,我们一种可行的方法就是在没有匹配上任何一个串的串后面接上当前串,那么概率就是\(f_0*\frac{1}{2^m}\),然而这个东西显然会比\(f_i\)要大,因为这个终止串再接上当前串可能包含了其他的串\(j\),而\(f_0\)表示的串没有匹配上任何一个串,意味着\(j\)的后缀是\(i\)的前缀。那么考虑所有其他串与当前串前后缀的匹配长度\(k\),我们可以列出方程:
\[f_0*\frac{1}{2^m}=f_i+\sum_j f_j*\frac{1}{2^{m-k}}\]
而然这样子是\(n+1\)元,\(n\)个方程,再利用\(\sum f_i=1\)补足最后一个方程即可。
好神仙啊。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
#define ull unsigned long long
#define MAX 320
const ull base=233;
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,m;char ch[MAX];
ull h[MAX][MAX],pw[MAX];
ull geths(int x,int l,int r){return h[x][r]-h[x][l-1]*pw[r-l+1];}
double g[MAX][MAX],bin[MAX];
void Guass()
{
    for(int i=0;i<=n;++i)
    {
        int p=i;
        for(int j=i+1;j<=n;++j)if(fabs(g[j][i])>fabs(g[p][i]))p=j;
        swap(g[p],g[i]);
        double t=g[i][i];
        for(int j=i;j<=n+1;++j)g[i][j]/=t;
        for(int j=i+1;j<=n;++j)
        {
            double t=g[j][i];
            for(int k=0;k<=n+1;++k)g[j][k]-=g[i][k]*t;
        }
    }
    for(int i=n;i;--i)
    {
        g[i][n+1]/=g[i][i];
        for(int j=i-1;j;--j)
            g[j][n+1]-=g[i][n+1]*g[j][i];
    }
}
int main()
{
    n=read();m=read();pw[0]=bin[0]=1;
    for(int i=1;i<=m;++i)pw[i]=pw[i-1]*base,bin[i]=bin[i-1]/2;
    for(int i=1;i<=n;++i)
    {
        scanf("%s",ch+1);
        for(int j=1;j<=m;++j)h[i][j]=h[i][j-1]*base+ch[j];
    }
    g[0][n+1]=1;
    for(int i=1;i<=n;++i)
    {
        g[0][i]=1;g[i][0]=-bin[m];
        for(int j=1;j<=n;++j)
            for(int k=1;k<=m;++k)
                if(geths(i,1,k)==geths(j,m-k+1,m))
                    g[i][j]+=bin[m-k];
    }
    Guass();
    for(int i=1;i<=n;++i)printf("%.10lf\n",g[i][n+1]);
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/10076926.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值