0%

动物朋友

题目链接

分析

首先构建AC自动机,于是题目转化为:在自动机上走L步,不碰到任何一个成功匹配的节点。
令DP[u][L]表示当前状态位于状态u,还需要走L步,满足条件的概率,记忆化搜索即可。

这道题的思路官方题解已经写得很清楚了,不再赘述。
直接上代码,代码中注释蛮清楚的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

#include<bits/stdc++.h>
#define x first
#define y second
#define ok cout << "ok" << endl;
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const double PI = acos(-1.0);
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int N=1e5+9;
const int shift=1e3+9;
const double Eps=1e-7;

int T, L, n;
char s[109], c[109];
double p[109];

struct acAuto {
int next[2009][128], fail[2009], root, L;
bool end[2009];
double dp[2009][109];
//初始化
void init() {
L = 0;
root = newNode();
}
//建立新节点
int newNode() {
for(int i = 0; i < 128; i++)
next[L][i] = -1;
end[L] = false;
return L++;
}
//建立Trie
void insert(char s[]) {
int now = root, len = strlen(s);
for(int i = 0; i < len; i++) {
if(next[now][s[i]] == -1)
next[now][s[i]] = newNode();
now = next[now][s[i]];
}
end[now] = true;
}
//构建AC自动机的fail数组,或者称为next数组
void build() {
fail[root] = root;
queue<int> que;
for(int i = 0; i < 128; i++) {
if(next[root][i] == -1)
next[root][i] = root;
else {
fail[next[root][i]] = root;
que.push(next[root][i]);
}
}
while(que.size()) {
int now = que.front();
que.pop();
for(int i = 0; i < 128; i++) {
if(next[now][i] == -1)
next[now][i] = next[fail[now]][i];
else {
fail[next[now][i]] = next[fail[now]][i];
que.push(next[now][i]);
}
}
}
}
//记忆化搜索实现DP,now表示现在的状态,L表示在AC自动机还需要走的步数
//初始状态为AC自动机的root节点,表示一步都没走,一个字符都没有
//边界条件为L=0,表示走完了,这时成功的概率是1
//否则,就需要枚举拼接在当前状态后的字符,并判断拼接后是否合法(合法就是全部模式串都不匹配),增加贡献值
//DP[u][L],其中u表示状态,L表示步数,即状态数为uL,状态转移是在AC自动机上跑的,看跳转次数而定,不大,所以总时间复杂度为O(uL)
double dfs(int now, int L) {
if(dp[now][L] != -1) return dp[now][L];
if(L == 0) return 1;
double ans = 0;
for(int i = 0; i < n; i++) {
int temp = next[now][c[i]];
if(!pipei(temp))
ans += p[i] * dfs(temp, L-1);
}
return dp[now][L] = ans;
}
//若有一个模式串匹配,返回1,若都不匹配,返回0
bool pipei(int now) {
while(now != root) {
if(end[now])
return true;
now = fail[now];
}
return false;
}
double solve(int L) {
for(int i = 0; i < 2009; i++)
for(int j = 0; j <= L; j++)
dp[i][j] = -1;
return dfs(0, L);
}
}ac;

int main(void) {
if(fopen("in", "r")!=NULL) {freopen("in", "r", stdin); freopen("out", "w", stdout);}
printf("Aonyx cinerea\n");
scanf("%d", &T);
while(T--) {
ac.init();
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%s", s);
ac.insert(s);
}
ac.build();
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf(" %c%lf", &c[i], &p[i]);
scanf("%d", &L);
printf("%.6f\n", ac.solve(L));
}
return 0;
}