BZOJ 1444: [Jsoi2009]有趣的游戏

一道重拾AC自动机的题,可以看作BZOJ 4820:[Sdoi2017]硬币游戏的弱化版

考虑一个暴力的想法,我们把所有串扔进一个AC自动机里,然后在fail树上跑DP,假设\(f_x\)表示节点\(x\)表示的状态出现的概率(即经过点\(x\)的概率),那么有:

\[f_{ch_{x,i}}=\sum_{i=0}^{m-1} p_i\times f_{x} \]

由于AC自动机的节点特性,这个转移成环了,因此上一波高斯消元,看似顺利解决?

写完跑一跑发现TM的概率都是\(0\)啊,这是什么鬼?

让我们仔细想一想,初始时经过根节点的概率\(f_0=1\),但是从根节点转移出去之后又有可能走回来,可是根节点的概率最大就是\(1\),这样就没法处理了!

那么这道题我们就不能从概率的角度入手了,关于经过多次的问题我们考虑期望

我们重新令\(f_x\)表示经过节点\(x\)的期望次数,这样有一个显而易见的好处:当我们走到单词的结尾时,游戏直接结束,那么就意味着走到它的期望次数就是走到它的概率

然后我们发现这个时候对于根节点我们就有办法处理了,\(f_0=1+\sum_{i=1}^{tot} f_i\times (\text{i走到0的概率})\),其它的点同理,只是不用加\(1\)罢了

然后再上高消就没有问题了,复杂度\(O((nl)^3)\),可以轻松通过

PS:注意特判所有人都赢不了的情况,否则你就会得到大量的nan

PPS:注意精度,尤其是输出-0.00的问题

#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
const double EPS=1e-10;
int n,l,m,x,y,cur; char s[N],pos[N]; double g[N],p[N][N],val[N];
inline void Gauss(CI n)
{
    RI i,j,k; for (i=0;i<=n;++i)
    {
        int mx=i; for (j=i;j<=n;++j) if (fabs(p[j][i])>=fabs(p[mx][i]))
        mx=j; swap(p[mx],p[i]); swap(val[mx],val[i]);
        for (j=i+1;j<=n;++j) if (fabs(p[j][i])>=EPS)
        {
            double dv=1.0*p[j][i]/p[i][i]; val[j]-=val[i]*dv;
            for (k=i;k<=n;++k) p[j][k]-=p[i][k]*dv;
        }
    }
    for (val[n]/=p[n][n],i=n-1;~i;--i)
    {
        for (j=i+1;j<=n;++j) val[i]-=p[i][j]*val[j];
        val[i]/=p[i][i];
    }
}
class AC_Automation
{
    private:
        struct ac_node
        {
            int ch[26],fail;
        }node[N]; int tot,q[N]; bool end[N];
        #define next(x,y) node[x].ch[y]
        #define fail(x) node[x].fail
        inline void get_fail(void)
        {
            RI i,j,H=0,T=0; for (i=0;i<m;++i) if (next(0,i)) q[++T]=next(0,i);
            while (H<T)
            {
                int now=q[++H],to; for (i=0;i<m;++i) if (to=next(now,i))
                end[to]|=end[fail(q[++T]=to)=next(fail(now),i)];
                else next(now,i)=next(fail(now),i); 
            }
            //for (i=0;i<=tot;++i) printf("%d\n",fail(i));
        }
    public:
        inline void insert(char *s,CI id)
        {
            int now=0; for (RI i=1;i<=l;++i)
            !next(now,s[i]-'A')&&(next(now,s[i]-'A')=++tot),now=next(now,s[i]-'A');
            pos[id]=now; end[now]=1;
        }
        inline void solve(void)
        {
            get_fail(); for (RI i=0,j;i<=tot;++i)
            {
                p[i][i]=-1.0; if (end[i]) continue;
                for (j=0;j<m;++j) p[next(i,j)][i]+=g[j];
            }
            val[0]=-1.0; Gauss(tot);
        }
        #undef next
        #undef fail
}AC;
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    RI i,j; for (scanf("%d%d%d",&n,&l,&m),i=0;i<m;++i)
    scanf("%d%d",&x,&y),g[i]=1.0*x/y; for (i=1;i<=n;++i)
    {
        for (scanf("%s",s+1),AC.insert(s,i),j=1;j<=l;++j)
        if (fabs(g[s[j]-'A'])<EPS) { ++cur; break; }
    }
    if (cur==n) { for (i=1;i<=n;++i) puts("0.00"); return 0; }
    for (AC.solve(),i=1;i<=n;++i)
    {
        if (fabs(val[pos[i]])<EPS) puts("0.00");
        else printf("%.2lf\n",val[pos[i]]);
    }
    return 0;
}
posted @ 2020-01-31 21:40  空気力学の詩  阅读(205)  评论(5编辑  收藏  举报