题目链接:
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2463分析:
给出 k 个模式串,再给出N个字符以及每个字符被选中的概率 pi,求选出N个字符组成长度为L的字符串且不含任何模式串的概率。
(出现的字符为 a-z, A-Z, 0-9)题解:
这题需要用到AC自动机的静态建树,用给出的N个字符建立深度为L最大长度的字典树,然后每个字符节点的值初始化为0,插入模式串时,在单词结尾的节点赋值为1。用DP[u, i]表示长度为u的目标串,结尾为i的概率;从根节点开始往下遍历,如果当前节点的值不1,就乘以当前节点的字符的概率,直到目标串的深度为L。
静态建树与动态建树的主要区别在于插入和删除操作,前者每次插入一个新节点当不存在相应字符时就利用实现已经创建好的数组存放,后者则动态申请一个节点。删除操作,前者直接将根节点的next全部置为NULL即可,后者要释放所有动态申请的节点空间。查询操作基本上一样。
AC自动机静态建树模板:
const int SIGMA_SIZE = 256;//每一个节点下由多少子节点
const int MAX_NODE = 22*256;//总的节点数
int n;//此题中给出单个字符的数量
struct Ac
{
int ch[MAX_NODE][SIGMA_SIZE];//静态建树
int fail[MAX_NODE];
int val[MAX_NODE]; //每个节点的值
int tot;
void init()//初始化字典树
{
tot = 1; //树的节点数
val[0] = 0;
memset(ch[0],0,sizeof(ch[0]));
}
void insert(char *s) //插入字符
{
int len = strlen(s);
int u = 0;
for(int i = 0;i<len;i++)
{
int c = s[i];
if(!ch[u][c])
{
val[tot] = 0;
memset(ch[tot],0,sizeof(ch[tot]));
ch[u][c] = tot++;
}
u = ch[u][c];
}
val[u] = 1;
}
void get_fail()//分配失配指针
{
queue <int> q;
fail[0] = 0;
for(int i = 0;i<SIGMA_SIZE;i++)
{
int u = ch[0][i];
if(u)
{
q.push(u);
fail[u] = 0;
}
}
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = 0;i<SIGMA_SIZE;i++)
{
int v = ch[u][i];
if(!v)
{
ch[u][i] = ch[fail[u]][i];
continue;
}
q.push(v);
int j = fail[u];
while(j && !ch[j][i]) j = fail[j];
j = ch[j][i];
fail[v] = j;
val[v] |= val[fail[v]];
}
}
}
double get_prob(int u,int l)//此题中DP获取长度为L满足条件的字符串的概率
{
if(!l) return 1;
if(vis[u][l]) return d[u][l];
vis[u][l] = 1;
double &ans = d[u][l];
ans = 0;
for(int i = 0;i<n;i++)
if(!val[ch[u][chara[i]]]) ans += prob[i] * get_prob(ch[u][chara[i]],l-1);
return ans;
}
}ac;
- 参考代码:
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int SIGMA_SIZE = 256;
const int MAX_NODE = 22*256;
int vis[MAX_NODE][111];
double d[MAX_NODE][111];
int chara[111];
double prob[111];
int n;
struct Ac
{
int ch[MAX_NODE][SIGMA_SIZE];
int fail[MAX_NODE];
int val[MAX_NODE];
int tot;
void init()
{
tot = 1;
val[0] = 0;
memset(ch[0],0,sizeof(ch[0]));
}
void insert(char *s)
{
int len = strlen(s);
int u = 0;
for(int i = 0;i<len;i++)
{
int c = s[i];
if(!ch[u][c])
{
val[tot] = 0;
memset(ch[tot],0,sizeof(ch[tot]));
ch[u][c] = tot++;
}
u = ch[u][c];
}
val[u] = 1;
}
void get_fail()
{
queue <int> q;
fail[0] = 0;
for(int i = 0;i<SIGMA_SIZE;i++)
{
int u = ch[0][i];
if(u)
{
q.push(u);
fail[u] = 0;
}
}
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = 0;i<SIGMA_SIZE;i++)
{
int v = ch[u][i];
if(!v)
{
ch[u][i] = ch[fail[u]][i];
continue;
}
q.push(v);
int j = fail[u];
while(j && !ch[j][i]) j = fail[j];
j = ch[j][i];
fail[v] = j;
val[v] |= val[fail[v]];
}
}
}
double get_prob(int u,int l)
{
if(!l) return 1;
if(vis[u][l]) return d[u][l];
vis[u][l] = 1;
double &ans = d[u][l];
ans = 0;
for(int i = 0;i<n;i++)
if(!val[ch[u][chara[i]]]) ans += prob[i] * get_prob(ch[u][chara[i]],l-1);
//printf("u = %d,l = %d,ans = %f\n",u,l,ans);
return ans;
}
}ac;
char str[22][22];
int main()
{
int T;
scanf("%d", &T);
int ttt =1;
while(T--)
{
ac.init();
int k;
scanf("%d",&k);
for(int i = 0;i<k;i++)
{
scanf("%s",str[i]);
ac.insert(str[i]);
}
scanf("%d",&n);
char tt[5];
for(int i = 0;i<n;i++)
{
scanf("%s%lf",tt,&prob[i]);
chara[i] = tt[0];
}
int l;
scanf("%d",&l);
ac.get_fail();
memset(vis,0,sizeof(vis));
printf("Case #%d: %.6f\n",ttt++,ac.get_prob(0,l));
}
return 0;
}