【BZOJ4820】硬币游戏(SDOI2017)-概率+高斯消元+KMP

测试地址:硬币游戏
做法:本题需要用到概率+高斯消元+KMP。
一看到这题,我们很快想到用JSOI2009-有趣的游戏那题一样,先建AC自动机,然后在AC自动机上建转移图,再高斯消元解方程……但是看到残酷的数据范围,我们就知道我们必须另辟蹊径了。
因为高斯消元的复杂度已经不能再降了,于是复杂度的瓶颈就在于方程太多了。因为我们要求的是 p(si) p ( s i ) ,即以 si s i 结尾且仅在字符串结尾匹配上串 si s i 的概率,而这样的概率只有 n n 个,那么我们如果令N为其他所有没匹配上的状态,如果我们能列出方程的话,方程的个数就是 n+1 n + 1 个,可以接受。
考虑在一个没匹配到任何串的串 N N 后面接一个串si,那么一旦接完这个游戏就会立刻停止,但是不一定在接完 si s i 后游戏才停止,有可能在接完之前先匹配上一个串。也就是说, N N 的一个后缀和si的一个前缀组成了一个 sj s j 。那么这个字符串就可以表示成, N N 的一个前缀+sj+si的一个后缀。因为 N N 的一个前缀+sj这一部分出现的概率就等于 p(sj) p ( s j ) ,而在后面接出一个特定长度 l l 01串的概率是 12l 1 2 l ,所以我们令 si s i 剩下的后缀长度为 l l ,这种情况出现的概率就是12lp(sj)
那么,对于每个串 sj s j ,对于每种它的一个后缀和 si s i 的一个前缀的匹配,设匹配长度为 l l ,发生的概率都是12mlp(sj),而对于所有的这些情况,发生的概率总和显然是 12mp(N) 1 2 m p ( N ) (即出现 N+si N + s i 这个串的概率),那么我们就可以列出一个方程了。对于每个 si s i ,我们都可以列出一个这样的方程,但是现在未知数有 n+1 n + 1 个(因为有 p(N) p ( N ) ),方程只有 n n 个,无法求解。这时候我们发现有一个隐含条件:p(si)=1,把这个当做方程,就可以高斯消元解方程组了,时间复杂度为 O(n3) O ( n 3 )
现在我们的问题就是求出 sj s j si s i 方程的贡献,我们其实只要将 si s i 作为模式串做KMP,那么最后匹配到的那个前缀以及能通过 next n e x t 指针走到的所有前缀,都是 sj s j 的一个后缀,累加贡献即可,时间复杂度为 O(n3) O ( n 3 )
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,nxt[310];
char s[310][310];
long double tot[310],g[310][310]={0},pwr[310];

void build(int i)
{
    nxt[0]=-1;tot[0]=pwr[m-1];
    int last;
    for(int j=1;j<m;j++)
    {
        last=nxt[j-1];
        while(last!=-1&&s[i][last+1]!=s[i][j]) last=nxt[last];
        if (s[i][last+1]==s[i][j]) nxt[j]=last+1;
        else nxt[j]=last;
        if (s[i][last+1]==s[i][j]) tot[j]=tot[nxt[j]]+pwr[m-j-1];
        else tot[j]=pwr[m-j-1];
    }
}

void calc(int i,int j)
{
    int last=-1;
    for(int k=0;k<m;k++)
    {
        while(last!=-1&&s[i][last+1]!=s[j][k]) last=nxt[last];
        if (s[i][last+1]==s[j][k]) last++;
    }
    if (last!=-1) g[i][j]=tot[last];
}

void gauss(int n)
{
    for(int i=1;i<=n;i++)
    {
        int mx=i;
        for(int j=i+1;j<=n;j++)
            if (fabs(g[j][i])>fabs(g[mx][i])) mx=j;
        for(int j=i;j<=n+1;j++)
            swap(g[i][j],g[mx][j]);
        for(int j=i+1;j<=n;j++)
        {
            for(int k=i+1;k<=n+1;k++)
                g[j][k]-=g[j][i]*g[i][k]/g[i][i];
            g[j][i]=0.0;
        }
    }
    for(int i=n;i>=1;i--)
        for(int j=1;j<i;j++)
        {
            g[j][n+1]-=g[j][i]*g[i][n+1]/g[i][i];
            g[j][i]=0.0;
        }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",s[i]);

    pwr[0]=1.0;
    for(int i=1;i<=m;i++)
        pwr[i]=pwr[i-1]*0.5;
    for(int i=1;i<=n;i++)
    {
        build(i);
        for(int j=1;j<=n;j++)
            calc(i,j);
    }
    for(int i=1;i<=n;i++)
        g[i][n+1]=-pwr[m];
    for(int i=1;i<=n;i++)
        g[n+1][i]=1.0;
    g[n+1][n+2]=1.0;

    gauss(n+1);
    for(int i=1;i<=n;i++)
        printf("%.10Lf\n",g[i][n+2]/g[i][i]);

    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值