Description
给定 (n) 个字符 (c_1, c_2, cdots c_n) 和各自的选择概率 (p_1, p_2, cdots, p_n),随机选择 (L) 次得到的随机字符串 (S) (每次独立随机)。给出 (K) 个模板串 (t_1, t_2, cdots, t_K),计算 (S) 不包含任何一个模板串的概率。
多组数据,组数为 (T)。
Hint
- (1le T le 50)
- (1le Kle 20)
- (1le |t_i|le 20)
- (c_i in [ exttt{a}, exttt{z}] cup [ exttt{A}, exttt{Z}] cup [ exttt{0}, exttt{9}])
- (1le L le 100)
- (sum_{i = 1}^n p_i = 1)
- (forall i in [1, K], t_i) 由 (n) 个给定字符组成。
Solution
AC 自动机 + 动态规划
首先肯定是对所有 模板串 建 AC自动机,在模式串结尾标记一下表示不能走。然后按之类问题的套路来:
设 (f(i, x)) 为字符串长度为 (i),走到结点 (x) 且不走到标记结点的概率。
那么自然是 (f(i - 1, x)) 转移到 (f(i, delta(x, c))) 的方向。状态转移方程:
[f(i, y) = sumlimits_{delta(x, c) = y} f(i - 1, x) imes ext{prob}(c)
]
其中 ( ext{prob}(c)) 表示选择字符 (c) 的概率。显然如果 (y) 点北标记为结尾就要跳过。
像这种 dp 会有一个坑:当一个结点的 fail 对应的结点是被标记了的,那么这个结点也应该被标记。为什么呢?因为 fail 指向的是当前的最长后缀,而一个以一个模板串作为后缀的字符串自然也不合法。
Code
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : UVA 11468 Substring
*/
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int P_L = 25;
const int M_L = 105;
const int K = 25;
const int S = 62;
int k, n, l;
double prob[S];
inline int trans(char c) {
if (isdigit(c)) return c - '0';
else if (islower(c)) return c - 'a' + 10;
else return c - 'A' + 36;
}
namespace ACAM {
const int T = K * P_L;
struct Node {
int ch[S];
int fail;
bool statu;
} t[T];
double f[M_L][T];
int total;
void restart() {
using namespace ACAM;
memset(f, 0, sizeof f);
memset(t, 0, sizeof t);
total = 0;
}
void insert(char* s) {
int x = 0;
for (register int i = 0; s[i]; i++) {
int c = trans(s[i]);
if (!t[x].ch[c]) t[x].ch[c] = ++total;
x = t[x].ch[c];
}
t[x].statu = true;
}
void build_fail() {
queue<int> Q;
for (register int i = 0; i < S; i++)
if (t[0].ch[i]) Q.push(t[0].ch[i]), t[t[0].ch[i]].fail = 0;
while (!Q.empty()) {
int x = Q.front(); Q.pop();
for (register int c = 0; c < S; c++)
if (t[x].ch[c]) {
Q.push(t[x].ch[c]);
t[t[x].ch[c]].fail = t[t[x].fail].ch[c];
} else t[x].ch[c] = t[t[x].fail].ch[c];
t[x].statu |= t[t[x].fail].statu; // 此坑的解决方案在此体现
}
}
double solve(int l) {
f[0][0] = 1.0;
for (register int i = 1; i <= l; i++)
for (register int x = 0; x <= total; x++)
for (register int c = 0; c < S; c++)
if (!t[t[x].ch[c]].statu)
f[i][t[x].ch[c]] += f[i - 1][x] * prob[c];
double ret = 0.0;
for (register int i = 0; i <= total; i++)
ret += f[l][i];
return ret;
}
};
char str[M_L];
signed main() {
int total_tc;
scanf("%d", &total_tc);
for (int tc = 1; tc <= total_tc; tc++) {
ACAM::restart();
memset(prob, 0, sizeof prob);
scanf("%d", &k);
for (register int i = 1; i <= k; i++) {
scanf("%s", str);
ACAM::insert(str);
}
scanf("%d", &n);
for (register int i = 1; i <= n; i++) {
char lt[5]; double pb;
scanf("%s%lf", lt, &pb);
prob[trans(lt[0])] += pb;
}
ACAM::build_fail();
scanf("%d", &l);
double ans = ACAM::solve(l);
printf("Case #%d: %.6f
", tc, ans);
}
return 0;
}