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
#include
#include
#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

你可能感兴趣的:(BZOJ 1444: [Jsoi2009]有趣的游戏)