dtoj1721. 字符串生成器 ( strgen )
1721. 字符串生成器 ( strgen )
有一个字符串生成器,初始时生成的字符串为空串,它每次按照给定概率随机生成一个小写字母,加在当前已生成字符串的后面。
给定N个长度为L的字符串,每个字符串由小写字母组成。
如果在某个时候,发现每个给定字符串都在当前已生成的字符串中作为子串出现过,生成器就会停下来,将当前生成的字符串作为输出。
求输出字符串长度的期望值。
Sol
考虑AC自动机。
我们设f[k][S]表示当前走到的节点是k,已经完成的串为集合S,到最终状态的期望步数。
那么可以发现f[k][S]是若干方程。
我们按S分层,那么层与层之间的边是单向的,只有S连向S的子集。
那么我们可以一层层高斯消元,对于跨层的边我们先算出上一层的答案,这层中当成常数就行。
这题卡精!!!
#include<cstdio> #include<iostream> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define maxn 55 #define eps 1e-7 using namespace std; int T,n,l,c,tr[maxn][10],tot,fail[maxn]; int id[maxn]; double p[10],a[maxn][maxn],f[maxn][1<<10],ans[maxn]; char ch[maxn]; void Q(){ for(int i=0;i<=tot;i++){ fail[i]=0;id[i]=0; for(int j=0;j<c;j++)tr[i][j]=0; for(int j=0;j<(1<<n);j++)f[i][j]=0; } tot=0; } bool ins(int x){ int k=0; for(int i=0;i<l;i++){ if(!tr[k][ch[i]-'a'])tr[k][ch[i]-'a']=++tot; k=tr[k][ch[i]-'a']; } if(id[k])return 0; id[k]=1<<(x-1);return 1; } queue<int>q; void build(){ for(int i=0;i<c;i++)if(tr[0][i])q.push(tr[0][i]); while(!q.empty()){ int k=q.front();q.pop(); for(int i=0;i<c;i++){ if(tr[k][i])fail[tr[k][i]]=tr[fail[k]][i],q.push(tr[k][i]); else tr[k][i]=tr[fail[k]][i]; } } } void Guess(){ int N=tot; for(int i=0;i<=N;i++){ int Max=i; //for(int j=i;j<=N;j++)if(fabs(a[j][i])>fabs(a[Max][i]))Max=j; for(int j=i;j<=N+1;j++)swap(a[i][j],a[Max][j]); if(fabs(a[i][i])<eps)continue; for(int j=i+1;j<=N;j++){ double tmp=a[j][i]/a[i][i]; for(int k=i;k<=N+1;k++)a[j][k]-=a[i][k]*tmp; } } for(int i=N;i>=0;i--){ ans[i]=-a[i][N+1]/a[i][i]; for(int j=i-1;j>=0;j--){ a[j][N+1]+=a[j][i]*ans[i]; } } } void work(){ Q(); scanf("%d%d%d",&n,&l,&c); for(int i=1;i<=n;i++){ scanf(" %s",ch); if(!ins(i))n--,i--; } build(); for(int i=0,t;i<c;i++){ scanf("%d",&t);p[i]=1.0*t/10000; } int N=(1<<n)-1; for(int S=N-1;S>=0;S--){ for(int i=0;i<=tot;i++) for(int j=0;j<=tot+1;j++)a[i][j]=0; for(int i=0;i<=tot;i++){ for(int j=0;j<c;j++){ int v=tr[i][j]; a[i][v]+=p[j]; if(id[v]&&(!(S&id[v]))){ a[i][v]-=p[j]; a[i][tot+1]+=(f[v][S|id[v]])*p[j]; } } a[i][i]--;a[i][tot+1]++; } Guess(); for(int i=0;i<=tot;i++)f[i][S]=ans[i]; } printf("%.5lf\n",f[0][0]); } int main(){ for(scanf("%d",&T);T--;work()); return 0; }