题意:给出一些字符和各自对应的选择概率,随机选择L次后将得到一个长度为L的随机字符串S.给出K个模版串,计算S不包含任何一个串的概率
分析:构造AC自动机之后,没随机生成一个字母,相当于在AC自动机中随机走一步.所有单词结点标记为"禁止",则本题就是求从结点0开始走L步,不进入任何静止结点的概率.
d[i][j]表示当前在结点i,还要走j步,不碰到任何禁止结点的概率.
在计算last的语句后面加一个val[u]|=val[f[u]],来计算禁止结点
按照书上说的,令dp[u][L]表示当前在结点u(0<=u<maxnode),还需要走L步,且不进入任何禁止结点的概率。
这里令val[u]=0表示“非禁止结点”,val[u]=1表示“禁止结点”。
说一下状态转移方程是怎么来的:
设当前结点标号为u,则下一层的标号是ch[u][c](这里c需要枚举所有字母0<=c<sigma_size),我们要选择这其中所有的“非禁止结点”,其标号记为v,表示的字母记为c,令当前dp[u][L]加上p[c]*dp[v][L-1](这里用了全概率公式),而求dp[v][L-1]需要记忆化搜索。
于是状态转移方程就是这个样子:
所以算一下dp[0][L]就是答案了。
然后再解释下什么叫做禁止结点以及val[u] |= val[ fail[u] ]这句话的作用。
禁止结点不单指一个单词的结尾结点(这个地方我卡了很久,一直不理解val[u] |= val[ fail[u] ]这句话是干嘛用的),还可能是一个模式串是另一个模式串的子串的情况。举个例子,abcd和bc,一开始令abcd的末尾结点d与bc的末尾结点c为禁止结点(val=1),然后还要注意到,abcd中包含了bc,因此abcd中的这个c也应该被设置为禁止结点,否则就算不到达d,前面的abc也会包含不允许出现的模式串bc。下面这个图可以说明这个情况:
从左图到右图可以注意到在设置fail指针的时候abcd的c结点的val值会被bc中的c结点“或”成1,表示abcd串内的c也是禁止结点。
当然这个时候我还产生了另外一个疑惑,我可以只出现cd不出现ab,这样abcd跟bc都无法组成,并不违反规则啊,这种情况会不会因为cd被设成禁止结点而被遗漏?我一开始也没想通(囧……),后来猛的发现,所谓的禁止结点是指该字符串上禁止到达的结点,也就是说,在abcd这个串里,必须通过ab才能到达cd,单纯的cd其实根本不会在abcd这条线路上出现,这条线路应该是root->c->d,而这显然是合法的因为模式串里并没有出现一个单词cd……
所以其实禁止结点就是每个单词的结尾结点以及模式串之间作为子串情况的结尾结点。
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int MAXNODE = 405;
const int SIGMA_SIZE = 62;
struct AutoMac {
int ch[MAXNODE][SIGMA_SIZE];
int val[MAXNODE];
int next[MAXNODE];
int sz;
double dp[MAXNODE][105];
double p[SIGMA_SIZE];
bool vis[MAXNODE][105];
void init() {
sz = 1;
memset(ch[0], 0, sizeof(ch[0]));
val[0] = 0;
memset(p, 0, sizeof(p));
memset(vis, false, sizeof(vis));
}
int idx(char c) {
if (c >= 'a' && c <= 'z') return c - 'a';
if (c >= 'A' && c <= 'Z') return c - 'A' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
}
void insert(char *s) {
int n = strlen(s);
int u = 0;
for (int i = 0; i < n; i++) {
int c = idx(s[i]);
if (!ch[u][c]) {
val[sz] = 0;
memset(ch[sz], 0, sizeof(ch[sz]));
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = 1;
}
void getnext() {
queue<int> Q;
next[0] = 0;
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[0][c];
if (u) {next[u] = 0; Q.push(u);}
}
while (!Q.empty()) {
int r = Q.front(); Q.pop();
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[r][c];
if (!u) {
ch[r][c] = ch[next[r]][c];
continue;
}
Q.push(u);
int v = next[r];
while (v && !ch[v][c]) v = next[v];
next[u] = ch[v][c];
val[u] |= val[next[u]];
}
}
}
double solve(int u, int L) {
if (vis[u][L]) return dp[u][L];
vis[u][L] = true;
if (!L) return dp[u][L] = 1;
dp[u][L] = 0;
for (int c = 0; c < SIGMA_SIZE; c++) {
if (val[ch[u][c]]) continue;
dp[u][L] += p[c] * solve(ch[u][c], L - 1);
}
return dp[u][L];
}
};
AutoMac gao;
int t, n, L;
char str[25];
int main() {
int cas = 0;
scanf("%d", &t);
while (t--) {
gao.init();
scanf("%d", &n);
while (n--) {
scanf("%s", str);
gao.insert(str);
}
gao.getnext();
scanf("%d", &n);
while (n--) {
scanf("%s", str);
scanf("%lf", &gao.p[gao.idx(str[0])]);
}
scanf("%d", &L);
printf("Case #%d: %.6lf\n", ++cas, gao.solve(0, L));
}
return 0;
}