题意:给定一些字符及其各自对应的选择概率,随机选择L次后将得到一个长度为L的随机字符串S(每次独立随机)。给出K个模板串,计算S不包含任何一个串的概率(即任意一个模板串都不是S的连续子串)
分析:
构造AC自动机,每次随机生成一个字母,相当于在AC自动机随机走一步。所以有单词标记的结点标记为“禁止”。本题就是求从结点0走L步,不进入任何禁止结点的概率。
假设d(i,j)表示在当前结点i,还需要走j步,不碰到任何禁止结点的概率。求解过程用记忆化搜索即可。
代码如下:
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <map>
using namespace std;
const int sigma_size = 64;
const int maxn = 500; //结点总数
const int maxs = 20+10; //模板个数
const int maxL = 105;
int idx[256];
double pro[sigma_size];
int K,L,n;
int sz; //结点总数
char p[maxs][maxs];
int ch[maxn][sigma_size];
int fail[maxn]; //fail函数
int match[maxn]; //是否包含某个字符串
queue<int>que;
double d[maxn][maxL];
bool vis[maxn][maxL];
void Insert(char *s){
int u = 0, len = strlen(s);
int c;
for (int i=0; i<len; i++){
c = idx[s[i]];
if (!ch[u][c]){
memset(ch[sz],0,sizeof(ch[sz]));
match[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
match[u] = 1;
}
void getFail(){
while (!que.empty()) que.pop();
fail[0] = 0;
int u,r,v;
for (int c=0; c<sigma_size; c++){
u = ch[0][c];
if (u) {fail[u] = 0; que.push(u);}
}
while (!que.empty()){
r = que.front(); que.pop();
for (int c=0; c<sigma_size; c++){
u = ch[r][c];
if (!u) {ch[r][c] = ch[fail[r]][c]; continue;}
que.push(u);
v = fail[r];
while (v && !ch[v][c]) v = fail[v];
fail[u] = ch[v][c];
match[u] |= match[fail[u]];
}
}
}
void init(){
char cc[9];
scanf("%d",&K);
for (int i=0; i<K; i++) scanf("%s",p[i]);
scanf("%d",&n);
for (int i=0; i<n; i++) {
scanf("%s %lf",cc,&pro[i]);
idx[cc[0]] = i;
}
sz = 1;
memset(ch[0],0,sizeof(ch[0]));
for (int i=0; i<K; i++) Insert(p[i]);
getFail();
scanf("%d",&L);
memset(vis,0,sizeof(vis));
}
double getProb(int u, int L){
if (!L) return 1.0;
if (vis[u][L]) return d[u][L];
vis[u][L] = 1;
double &ans = d[u][L];
ans = 0.0;
for (int i=0; i<n; i++) if (!match[ch[u][i]]) ans += pro[i]*getProb(ch[u][i],L-1);
return ans;
}
int main(){
int kase = 0;
int T;
scanf("%d",&T);
while (T--){
init();
printf("Case #%d: %.6lf\n",++kase,getProb(0,L));
}
return 0;
}