一道重拾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