【BZOJ1444】[JSOI2009] 有趣的游戏(AC自动机+高斯消元)
大致题意: 有\(n\)个由前\(m\)个大写字母组成的长度为\(l\)的字符串。现在随机生成一个字母序列,每次第\(i\)个字母有\(\frac{p_i}{q_i}\)的概率生成。求每个字符串作为首个出现在字母序列中的字符串的概率。
\(AC\)自动机
显然,首个出现的字符串必然是生成的字母序列的后缀,因而这可以看做是一个多模字符串匹配问题。
所以,我们就需要建出一个\(AC\)自动机。
考虑如何求每个字符串作为首个出现的字符串的概率,容易想到在\(AC\)自动机上\(DP\)。
于是,就变成了求这些字符串所对应的节点作为在它们之中首个出现的节点的概率。
但如果直接搞每个节点被经过的概率,似乎有点难处理,因为一个节点可能会经过多次。
所以我们把它修改为求每个点被经过的期望次数,且到达了这些字符串所对应的节点之后就不能再走。
为什么会有这样的想法呢?因为这就可以让我们把这个问题转化为一个十分相似的问题:【洛谷3232】[HNOI2013] 游走。
参照“【洛谷3232】[HNOI2013] 游走”这道题的做法,我们可以按同样的方式列出\(n\times l\)个方程,然后高斯消元。
在此题中,从节点\(x\)转移到它的第\(i\)个儿子的系数就是\(\frac {p_i}{q_i}\),其中\(x\)不能是题目中给出的那些字符串所对应的节点(此时转移系数都为\(0\))。
同样地,只有根节点等号右边的值是\(1\),其余节点等号右边的值皆为\(0\)。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 10
#define SZ 100
#define DB double
#define eps 1e-8
using namespace std;
int n,m,l,Nt,pos[N+5];DB p[N+5];char s[N+5][N+5];
namespace Gauss//高斯消元模板,这里略去了判无解的步骤
{
DB a[SZ+5][SZ+5],v[SZ+5];
I void Find(CI x)
{
RI i,j;for(i=x;fabs(a[i][x])<eps;++i);if(i==x) return;
for(swap(v[x],v[i]),j=x;j<=Nt;++j) swap(a[x][j],a[i][j]);
}
I void Solve()
{
RI i,j,k;DB t;for(i=1;i<=Nt;++i) for(Find(i),j=i+1;j<=Nt;++j)
for(t=-a[j][i]/a[i][i],v[j]+=t*v[i],k=i;k<=Nt;++k) a[j][k]+=t*a[i][k];
for(i=Nt;i;--i) for(v[i]/=a[i][i],j=i-1;j;--j) v[j]-=a[j][i]*v[i];
}
}
class AcAutomationMachine//纯粹的建AC自动机
{
private:
int rt,q[SZ+5];struct node {int T,F,S[N+5];}O[SZ+5];
public:
I AcAutomationMachine() {rt=Nt=1;}
I void Ins(CI id,char* s)//插入字符串
{
RI i,x=rt,nxt;for(i=1;i<=l;++i)
!O[x].S[nxt=s[i]&31]&&(O[x].S[nxt]=++Nt),x=O[x].S[nxt];
O[pos[id]=x].T=1;//在对应节点处打标记
}
I void GetNxt()//求出Fail指针
{
RI i,k,H=1,T=0;for(i=1;i<=m;++i)
(O[rt].S[i]?O[q[++T]=O[rt].S[i]].F:O[rt].S[i])=rt;
W(H<=T) for(k=q[H++],i=1;i<=m;++i)
(O[k].S[i]?O[q[++T]=O[k].S[i]].F:O[k].S[i])=O[O[k].F].S[i];
}
I void Calc()
{
for(RI i=1,j;i<=Nt;++i) if(!O[i].T) for(j=1;j<=m;++j) Gauss::a[O[i].S[j]][i]-=p[j];//建立方程
for(RI i=1;i<=Nt;++i) Gauss::a[i][i]+=1;Gauss::v[rt]=1;//只有根节点的方程等号右边为1
}
}AC;
int main()
{
RI i,x,y;scanf("%d%d%d",&n,&l,&m);
for(i=1;i<=m;++i) scanf("%d%d",&x,&y),p[i]=1.0*x/y;
for(i=1;i<=n;++i) scanf("%s",s[i]+1),AC.Ins(i,s[i]);AC.GetNxt(),AC.Calc();
for(Gauss::Solve(),i=1;i<=n;++i) printf("%.2lf\n",Gauss::v[pos[i]]);return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒