P6125 [JSOI2009]有趣的游戏
【题意】
给定n个长度为l的字符串,字符集大小为m,每次在末尾随机生成一个字符,当出现字符串的时候停止,求这n个字符串作为终止的概率
【分析】
我们能想到在串末尾位置为增加字符就很想AC自动机的转移方式,所以我们可以考虑建立出AC自动机
然后考虑问题就被转换为到AC自动机上某些点的概率
但是这样我们也很难去直接计算
因为一个点是一个串的结尾的话,就意味着游戏要结束了,所以我们的转移也会停止
到达某个点的概率就可以被到达次数的期望来代替
这时候我们就可以dp了,考虑每个非终止位置都有1/m的概率转移到儿子,列出方程高斯消元即可
注意根的位置要特判
$f_x=[x=0]+\sum_{from}f_{from}$
时间复杂度 $O(n^3l^3)$
【代码】
#include<bits/stdc++.h> using namespace std; typedef pair <int,double> PID; #define mp make_pair int n,l,m; double p[11]; char s[11]; const int maxn=105+5; struct ac { int ch[27],tag; }tr[maxn]; int cnt,pos[maxn]; vector <PID> G[maxn]; void insert(int x) { int now=0; for(int i=1;i<=l;i++) { int c=s[i]-'A'; if(!tr[now].ch[c]) tr[now].ch[c]=++cnt; now=tr[now].ch[c]; } tr[now].tag=1; pos[x]=now; } double A[maxn][maxn],res[maxn]; void guass(int n) { for(int i=1;i<=n;i++) { int pos=i; for(int j=i+1;j<=n;j++) if(fabs(A[j][i])>fabs(A[pos][i])) pos=j; for(int j=1;j<=n+1;j++) swap(A[i][j],A[pos][j]); for(int j=1;j<=n;j++) if(i!=j) { double t=A[j][i]/A[i][i]; for(int k=i+1;k<=n+1;k++) A[j][k]-=A[i][k]*t; } } for(int i=1;i<=n;i++) res[i]=A[i][n+1]/A[i][i]; } int fail[maxn]; void getfail() { queue <int> q; for(int i=0;i<m;i++) if(tr[0].ch[i]) q.push(tr[0].ch[i]); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=0;i<m;i++) { if(!tr[u].ch[i]) tr[u].ch[i]=tr[fail[u]].ch[i]; else { int to=fail[u]; fail[tr[u].ch[i]]=tr[to].ch[i]; q.push(tr[u].ch[i]); } } } } int main() { // freopen("game.in","r",stdin); scanf("%d%d%d",&n,&l,&m); int x,y; for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); p[i]=1.0*x/y; } for(int i=1;i<=n;i++) { scanf("%s",s+1); insert(i); } getfail(); for(int i=0;i<=cnt;i++) if(!tr[i].tag) for(int j=0;j<m;j++) G[tr[i].ch[j]].push_back(mp(i,p[j+1])); for(int i=0;i<=cnt;i++) { A[i+1][i+1]=-1.0; if(!i) A[i+1][cnt+2]=-1.0; for(int k=0;k<G[i].size();k++) A[i+1][G[i][k].first+1]+=G[i][k].second; } guass(cnt+1); // for(int i=0;i<=cnt+2;i++) // printf("%d ",G[i].size()); for(int i=1;i<=n;i++) printf("%.2f\n",max(0.0,res[1+pos[i]])); return 0; }