题目大意:
就是现在给出T组数据,每组由K个字符串(K <= 20) 每个字符串长度至少为1且不超过20, 现在有一个字符串的生成器,这个生成器生成字符串时每个位置上的字母出现的可能性是独立的, 现在给出有N个字母出现的概率(和为1), 问这个生成器生成一个长度为L的字符串时, 生成的字符串不包含这K的串中的任何一个的概率是多少
大致思路:
是个不错的题...当初因为不会AC自动机一直留着,现在回来一看应该属于简单题, 首先常规地建立AC自动机, 在状态转移图上用dp[i][j]表示当前走了i步之后处在节点j处,且没有走到过标记节点的概率(标记节点即为不能到达的插入的字符串的结尾), 那么不难发现状态转移方程 dp[i + 1][next[j][k]] += dp[i][j]*p[k] 其中p[k]为出现第k种字符的概率, 时间复杂度也不高,具体细节见代码, 注释较详细
代码如下:
Result : Accepted Memory : 0? KB Time : 549 ms
/*
* Author: Gatevin
* Created Time: 2015/1/29 12:51:15
* File Name: Iris_Fleyja.cpp
*/
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
char s[22];
double p[65];
int num[300];
int len;
double dp[110][500];
void init()//给可能出现的字符编号
{
for(int i = '0'; i <= '9'; i++) num[i] = i - '0';
for(int i = 'a'; i <= 'z'; i++) num[i] = i - 'a' + 10;
for(int i = 'A'; i <= 'Z'; i++) num[i] = i - 'A' + 36;
return;
}
struct Trie
{
int next[500][65], fail[500];
bool end[500];
int L, root;
int newnode()
{
for(int i = 0; i < 62; i++)
next[L][i] = -1;
end[L++] = 0;
return L - 1;
}
void init()
{
L = 0;
root = newnode();
return;
}
void insert(char *s)
{
int now = root;
for(; *s; s++)
{
if(next[now][num[*s]] == -1)
next[now][num[*s]] = newnode();
now = next[now][num[*s]];
}
end[now] = 1;
return;
}
void build()
{
fail[root] = root;
queue <int> Q;
Q.push(root);
while(!Q.empty())
{
int now = Q.front();
Q.pop();
end[now] |= end[fail[now]];//注意标记不能进入的点
for(int i = 0; i < 62; i++)
if(next[now][i] == -1)
next[now][i] = now == root ? root : next[fail[now]][i];
else
{
fail[next[now][i]] = now == root ? root : next[fail[now]][i];
Q.push(next[now][i]);
}
}
return;
}
void solve(int cas)
{
memset(dp, 0, sizeof(dp));
/*
* dp[i][j]表示走了i步之后停在节点j处没有经过不能进入的节点的概率
*/
dp[0][root] = 1;
for(int i = 0; i < len; i++)
for(int j = 0; j < L; j++)
{
if(end[j]) continue;//只有当j不是不可进入节点才可转移至下一步
for(int k = 0; k < 62; k++)
dp[i + 1][next[j][k]] += dp[i][j]*p[k];//普通的状态转移
}
double ans = 0;
for(int i = 0; i < L; i++)
if(!end[i]) ans += dp[len][i];
printf("Case #%d: %.6f\n", cas, ans);
return;
}
};
Trie AC;
int main()
{
int T, K, N;
char c;
init();
scanf("%d", &T);
for(int cas = 1; cas <= T; cas++)
{
scanf("%d", &K);
AC.init();
for(int i = 1; i <= K; i++)
{
scanf("%s", s);
AC.insert(s);
}
AC.build();
scanf("%d", &N);
memset(p, 0, sizeof(p));
for(int i = 1; i <= N; i++)
{
getchar();
scanf("%c", &c);
scanf("%lf", &p[num[c]]);
}
scanf("%d", &len);
AC.solve(cas);
}
return 0;
}