bzoj 1030: [JSOI2007]文本生成器

1030: [JSOI2007]文本生成器

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 3255   Solved: 1331
[ Submit][ Status][ Discuss]

Description

JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版。该软件可以随机生成一些文章―――总是生成一篇长度固定且完全随机的文章—— 也就是说,生成的文章中每个字节都是完全随机的。如果一篇文章中至少包含使用者们了解的一个单词,那么我们说这篇文章是可读的(我们称文章a包含单词b,当且仅当单词b是文章a的子串)。但是,即使按照这样的标准,使用者现在使用的GW文本生成器v6版所生成的文章也是几乎完全不可读的。 ZYX需要指出GW文本生成器 v6生成的所有文本中可读文本的数量,以便能够成功获得v7更新版。你能帮助他吗?

Input

输入文件的第一行包含两个正整数,分别是使用者了解的单词总数N (<= 60),GW文本生成器 v6生成的文本固定长度M;以下N行,每一行包含一个使用者了解的单词。 这里所有单词及文本的长度不会超过100,并且只可能包含英文大写字母A..Z  。

Output

一个整数,表示可能的文章总数。只需要知道结果模10007的值。

Sample Input

2 2
A
B

Sample Output

100

HINT

Source

[ Submit][ Status][ Discuss] 题解:AC自动机+dp

依次读入所以单词并把它们丢到trie树中,把每个单词的结尾标记一下,然后建立fail指针,在建立时需要注意如果某个点的fail有结尾标记,那么当前点也要相应的标记一下。

如果这道题直接求合法的文本个数,会比较难求。那么换一种想法。我们求出不合法的个数然后用可能生成的总文本个数减去不合法的个数即为答案,那么不合法的个数怎么求呢,我们在trie树的一些节点上打了标记,如果我们在构造文本的时候选用了某个带标记的节点,那么当前文本就合法了,我们要求不合法的,就需要避开打过标记的节点,用没有标记的节点去转移。

f[i][j] 表示文本的第i位,匹配到trie 树中的第j 个节点

状态转移方程: f[i][ch[k][j]]=(f[i][ch[k][j]]+f[i-1][k])%p  (ch[k][j]表示节点k的儿子)  转移时必须保证k 没有标记。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 60003
using namespace std;
int n,m,sz;
int fail[N],ch[N][30],isend[N],pos[N];
int f[120][6003];
const int p=10007;
char s[N];
void insert(int k)
{
 int len=strlen(s);
 int now=0;
 for(int i=0;i<len;i++)
 {
  int x=s[i]-'A';
  if (!ch[now][x]) ch[now][x]=++sz;
  now=ch[now][x]; 
 }
 isend[now]=1; pos[k]=now;
}
void makefail()
{
 queue<int> p;
 for (int i=0;i<26;i++)
 if (ch[0][i]) p.push(ch[0][i]);
 while (!p.empty())
 {
   int now=p.front(); p.pop();
   for (int i=0;i<26;i++)
   {
   	if (!ch[now][i])
   	{
   	 ch[now][i]=ch[fail[now]][i];
   	 continue;
    }
    int x=ch[now][i];
    fail[x]=ch[fail[now]][i];
    if (isend[fail[x]])   isend[x]=1;
    p.push(x);
   }
 }
}
int main()
{
 scanf("%d%d",&n,&m);
 for (int i=1;i<=n;i++)
 {
  scanf("%s",s);
  insert(i);
 }
 for (int i=0;i<26;i++) if (!ch[0][i]) ch[0][i]=++sz;//这里需要注意,因为在构造文本时‘A'-'Z'都可以选,所以在构造trie树时需要加上单词中不包含的字母,这样转移才是正确的
 makefail();
 f[0][0]=1;
 for (int i=1;i<=m;i++)
 {
   for (int j=0;j<=sz;j++)
    {
      if (isend[j]||!f[i-1][j]) continue;
      for (int k=0;k<26;k++)
       f[i][ch[j][k]]=(f[i][ch[j][k]]+f[i-1][j])%p;
    }
 }
 int ans=0; int ans1=1;
 for (int i=1;i<=m;i++)
  ans1=(ans1*26)%p;
 for (int i=1;i<=sz;i++)
  if (!isend[i])
   ans=(ans+f[m][i])%p;
 printf("%d\n",(ans1-ans+p)%p);
}



你可能感兴趣的:(bzoj 1030: [JSOI2007]文本生成器)