题意:
给出一个n个单词的字典,单词长度<=100;
求长度为m的随机字符串中有多少个串至少包括一个单词;
n<=60,m<=100;
题解:
好久没有写AC自动机啦,如今还记得模板真是难得;
然而这似乎是Trie图?总之写出来不差几句话;
首先至少包括一个单词这个条件不太好弄;
那就转化一下,求不含单词的字符串个数;
然后就想办法处理这个;
定义状态,f[i][j]表示长度为i的字符串,最后几位的状态在Trie图上的结点编号;
那么转移显然了:
最开始f[0][1]是1;
从一个点到下一个点累加f;
如果目前结点代表的单词在字典中,f值为0;
有个小坑,就是可能有单词包括另一个单词,这时也要将大单词里的小单词标上标记;
就是在找fail的时候顺便标一下,具体可以看代码;
不过代码很丑= =,我反正是写不出来比较短的AC自动机。。。
代码:
#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 128
#define M 6000
#define mod 10007
#define pr pair<int,int>
using namespace std;
int next[M][26],fail[M],root=1,tot=1;
int f[N][M];
char str[N];
bool is[M];
int pow(int x,int y)
{
int ret=1;
while(y)
{
if(y&1)
ret=ret*x%mod;
x=x*x%mod;
y>>=1;
}
return ret;
}
void Insert(char *s)
{
int p=root,index;
while(*s!='\0')
{
index=*s-'A';
if(!next[p][index])
next[p][index]=++tot;
p=next[p][index];
s++;
}
is[p]=1;
}
void Build()
{
queue<int>q;
int x,p,i,temp;
q.push(root);
while(!q.empty())
{
x=q.front(),q.pop();
for(i=0;i<26;i++)
{
p=next[x][i];
temp=fail[x];
while(temp)
{
if(next[temp][i])
{
if(p&&is[next[temp][i]])
is[p]=1;
(p?fail[p]:next[x][i])=next[temp][i];
break;
}
temp=fail[temp];
}
if(!temp) (p?fail[p]:next[x][i])=root;
if(p) q.push(p);
}
}
}
int main()
{
int n,m,i,j,k,index,ans;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%s",str);
Insert(str);
}
Build();
f[0][1]=1;
for(i=1;i<=m;i++)
{
for(j=1;j<=tot;j++)
{
for(k=0;k<26;k++)
{
index=next[j][k];
if(is[index]) continue;
f[i][index]=(f[i][index]+f[i-1][j])%mod;
}
}
}
for(i=1,ans=0;i<=tot;i++)
{
ans+=f[m][i];
ans%=mod;
}
printf("%d\n",((pow(26,m)-ans)%mod+mod)%mod);
return 0;
}