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

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

#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;
}
posted @ 2018-04-16 15:03  Maxwei_wzj  阅读(177)  评论(0编辑  收藏  举报