题意:给出一些字符和各自对应的选择概率,随机选择L次后将得到一个长度为L的字符串,同时给出一些模版串,计算该字符串不包含任何一个模版串的概率。
可以把随即选取字符看在ac自动机的节点和节点之间移动,所经过的边即代表所选的字符。同时ac自动机的失配指针和单词节点标记则为选取字符生成的字符串不包含模版串提供了方法:可以证明,若在移动中经过了一个节点,且该节点为单词节点或该节点失配指针路径上有单词节点,则该移动所生成的字符串一定包含了模版串。可以对用模版串形成的ac自动机进行深搜,同时避开上述节点,同时为了避免大量重复计算(dp(i, j)代表目前在节点i,且还需要走j步;可以看出在递归搜索中对同一个dp(i, j)肯定进行了重复的计算),可以用记忆化搜索,用数组将子问题的解保存起来,从而将时间从指数级别降为多项式级别。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 1000;
const int sigma_size = 62;
int t, k, n, l;
double proba[sigma_size];
double dp[maxn][105];
struct ac_machine
{
int ch[maxn][sigma_size];
int val[maxn];
int f[maxn];
int last[maxn];
int sz;
void init(){ sz = 1; memset(ch, 0, sizeof(ch)); memset(val, 0, sizeof(val)); memset(f, 0, sizeof(f)); memset(last, 0, sizeof(last)); }
int idx(char c)
{
if(c >= 'a' && c <= 'z') return c - 'a';
else if(c >= 'A' && c <= 'Z') return c - 'A' + 26;
else return c - '0' + 52;
}
void insert(char * s, int v)
{
int u = 0;
for(int i = 0; s[i]; ++i)
{
int c = idx(s[i]);
if(!ch[u][c])
{
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = v;
}
void getFail()
{
queue<int> q;
f[0] = 0;
for(int c = 0; c < sigma_size; ++c)
{
int u = ch[0][c];
if(u) { f[u] = 0; q.push(u); last[u] = 0; }
else ch[0][c] = 0;
}
while(!q.empty())
{
int r = q.front(); q.pop();
for(int c = 0; c < sigma_size; ++c)
{
int u = ch[r][c];
if(u)
{
q.push(u);
int p = f[r];
while(p && ch[p][c] == 0) p = f[p];
f[u] = ch[p][c]; last[u] = val[f[u]] ? f[u] : last[f[u]];
}
else ch[r][c] = ch[f[r]][c];
}
}
}
double dfs(int u, int ll)
{
if(dp[u][ll] != -1) return dp[u][ll];
if(ll == 0) return 1;
double res = 0;
for(int i = 0; i < sigma_size; ++i)
{
int v = ch[u][i];
if(proba[i] == 0 || val[v] || last[v]) continue;
res += proba[i] * dfs(v, ll - 1);
}
return dp[u][ll] = res;
}
}ac;
int main()
{
freopen("in.txt", "r",stdin);
scanf("%d", &t);
int cc = 1;
while(t--)
{
ac.init(); for(int i = 0; i < sigma_size; ++i) proba[i] = 0;
for(int i = 0; i < maxn; ++i) for(int j = 0; j < 105; ++j) dp[i][j] = -1;
scanf("%d", &k);
for(int i = 0; i < k; ++i)
{
char str[100];
scanf("%s", str);
ac.insert(str, 1);
}
scanf("%d", &n);
for(int i = 0; i < n; ++i)
{
getchar();
char c;
scanf("%c", &c);
scanf("%lf", &proba[ac.idx(c)]);
}
scanf("%d", &l);
ac.getFail();
printf("Case #%d: %.6lf\n", cc++, ac.dfs(0, l));
}
return 0;
}
/*
2
1
a
2
a 0.5
b 0.5
2
2
ab
ab
2
a 0.2
b 0.8
2
Case #1: 0.250000
Case #2: 0.840000
*/
本文介绍如何利用AC自动机结合记忆化搜索解决特定字符串匹配问题,通过构建AC自动机来高效查找模板串,并使用记忆化搜索避免重复计算,最终求得目标字符串不包含任何模板串的概率。
1561

被折叠的 条评论
为什么被折叠?



