题意
给出一些字符和各自对应的选择概率,随机选择L次后得到一个长度为L的随机字符串S(每次独立随机)。给出K个模板串,计算S不包含任何一个串的概率(即任何一个模板串都不是S的连续子串)。
题解
对K个模板串建立AC自动机。
随机选择一个长度为L的随机字符串S就相当于在AC自动机上走L步。
设dp[u][L]表示当前在u结点上还需走L步且不包含模板串的概率。
根据全概率公式:
dp[u][L]=pro[vi]*dp[vi][L-1]+pro[vj]*dp[vj][L-1]+…+dp[vk][L-1].
需要根据建立AC自动机的过程确定在u结点有哪些子结点是可以往下走的。
设ed[v]=0表示可以往v结点走。
首先每个模板串的路径字符串的尾结点是不能走的,然后v的后缀结点是不能走的结点的话,那么v结点也是不能走的。即ed[v]|=ed[fail[v]].
AC代码
//320ms
#include <bits/stdc++.h>
using namespace std;
const int maxn=22;
int getid(char c)
{
if(c<='z' && c>='a') return c-'a';
if(c<='Z' && c>='A') return c-'A'+26;
if(c<='9' && c>='0') return c-'0'+52;
}
double pro[62];
struct trie
{
int ch[maxn*20][62],ed[maxn*20],sz,fail[maxn*20];
double dp[maxn*20][110];
bool vis[maxn*20][110];
void init()
{
memset(ch[0],0,sizeof(ch[0]));
memset(vis,false,sizeof(vis));
memset(dp,0,sizeof(dp));
sz=0;
}
void insert(char *s)
{
int len=strlen(s),p=0;
for(int i=0;i<len;i++)
{
int c=getid(s[i]);
if(!ch[p][c])
{
ch[p][c]=++sz;
memset(ch[sz],0,sizeof(ch[sz]));
ed[sz]=0;
}
p=ch[p][c];
}
ed[p]=1;
}
void build()
{
queue<int> que;
for(int i=0;i<62;i++)
{
int &c=ch[0][i];
if(c) fail[c]=0,que.push(c);
else c=0;
}
while(!que.empty())
{
int now=que.front();que.pop();
int nxt=fail[now];
for(int i=0;i<62;i++)
{
int &c=ch[now][i];
if(c) fail[c]=ch[nxt][i],que.push(c),ed[c]|=ed[fail[c]];
else c=ch[nxt][i];
}
}
}
double dps(int u,int L)
{
if(!L) return 1.0;
if(vis[u][L]) return dp[u][L];
vis[u][L]=true;
double &ans=dp[u][L];
ans=0.0;
for(int i=0;i<62;i++)
{
int v=ch[u][i];
if(ed[v]) continue;
ans+=pro[i]*dps(v,L-1);
}
return ans;
}
}ac;
int main()
{
int T,kase=0;
scanf("%d",&T);
while(T--)
{
int k;
scanf("%d",&k);
ac.init();
while(k--)
{
char s[22];
scanf("%s",s);
ac.insert(s);
}
ac.build();
int n;
scanf("%d",&n);
for(int i=0;i<62;i++) pro[i]=0.0;
while(n--)
{
char s[2];
scanf("%s",s);
int c=getid(s[0]);
scanf("%lf",&pro[c]);
}
int L;
scanf("%d",&L);
double ans=ac.dps(0,L);
printf("Case #%d: %.6lf\n",++kase,ans);
}
return 0;
}