洛谷P4052 [JSOI2007]文本生成器 题解
题意:
JSOI 交给队员 ZYX 一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是 GW 文本生成器 v6 版。
该软件可以随机生成一些文章——总是生成一篇长度固定且完全随机的文章。 也就是说,生成的文章中每个字符都是完全随机的。如果一篇文章中至少包含使用者们了解的一个单词,那么我们说这篇文章是可读的(我们称文章 s s s 包含单词 t t t,当且仅当单词 t t t 是文章 s s s 的子串)。但是,即使按照这样的标准,使用者现在使用的 GW 文本生成器 v6 版所生成的文章也是几乎完全不可读的。ZYX 需要指出 GW 文本生成器 v6 生成的所有文本中,可读文本的数量,以便能够成功获得 v7 更新版。你能帮助他吗?
答案对 1 0 4 + 7 10^4 + 7 104+7 取模。
对于全部的测试点,保证:
- 1 ≤ n ≤ 60 1 \leq n \leq 60 1≤n≤60, 1 ≤ m ≤ 100 1 \leq m \leq 100 1≤m≤100。
- 1 ≤ ∣ s i ∣ ≤ 100 1 \leq |s_i| \leq 100 1≤∣si∣≤100,其中 ∣ s i ∣ |s_i| ∣si∣ 表示字符串 s i s_i si 的长度。
- s i s_i si 中只含大写英文字母。
显然要建 A C \tt{AC} AC 自动机。包含单词,比较麻烦。
考虑容斥,计算不包含单词的数量(不可读文本数量)
「可读文本数量」等于「所有文本数量」减去「不可读文本数量」
「所有文本数量」显然是 2 6 m 26^m 26m
不难发现,一个不可读文本在 A C \tt{AC} AC 自动机上跑的时候,是不会碰到「危险结点」的
「危险结点」包括「是字符串结尾」的结点和「某个或某些后缀是单词」的结点
危险结点的处理比较简单,只要在建自动机的时候处理一下就好了,具体看代码
然后这个题目是计数题,考虑dp
设 f i , j f_{i,j} fi,j 表示走到 j j j 号结点,文本长度为 i i i 时的方案数
显然
f
0
,
0
=
1
f_{0,0} = 1
f0,0=1
f
i
+
1
,
v
=
∑
(
u
,
v
)
∈
E
f
i
,
u
f_{i+1,v} = \sum_{(u,v) \in E} f_{i,u}
fi+1,v=(u,v)∈E∑fi,u
这里的
(
u
,
v
)
∈
E
(u,v) \in E
(u,v)∈E 表示在
A
C
\tt{AC}
AC 自动机上存在
u
u
u 指向
v
v
v 的边
「不可读文本数量」就等于 ∑ 0 ≤ i ≤ tot f m , i \sum_{0 \le i \le \text{tot}} f_{m,i} ∑0≤i≤totfm,i
时间复杂度 O ( 26 × m ∑ ∣ s i ∣ ) O(26 \times m \sum |s_i|) O(26×m∑∣si∣)
完整代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
#include <queue>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(105)
#define L (int)(6e3+15)
const int p=1e4+7;
void add(int &a,int b){a=(a%p+b%p)%p;}
int qpow(int a,int b)
{
int ans=1,base=a%p;
while(b)
{
if(b&1) ans=ans*base%p;
base=base*base%p;
b>>=1;
}
return ans;
}
struct Trie
{
queue<int> q;
int tot,trie[L][26],ed[L],fail[L],f[N][L];
void insert(string s)
{
int u=0;
for(int i=0; i<s.size(); i++)
{
int c=s[i]-'A';
if(!trie[u][c])trie[u][c]=++tot;
u=trie[u][c];
}
ed[u]=1;
}
void build()
{
for(int i=0; i<26; i++)
if(trie[0][i])q.push(trie[0][i]);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=0; i<26; i++)
{
if(trie[u][i])
{
fail[trie[u][i]]=trie[fail[u]][i];
if(ed[fail[trie[u][i]]])ed[trie[u][i]]=1;
q.push(trie[u][i]);
}else trie[u][i]=trie[fail[u]][i];
}
}
}
void solve(int m)
{
f[0][0]=1;
for(int i=0; i<m; i++)
for(int j=0; j<=tot; j++)
for(int k=0; k<26; k++)
if(!ed[trie[j][k]])
add(f[i+1][trie[j][k]],f[i][j]);
int res=qpow(26,m);
for(int i=0; i<=tot; i++)
res=((res-f[m][i])%p+p)%p;
cout << res << '\n';
}
}tr;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n,m; string s;
cin >> n >> m;
for(int i=1; i<=n; i++)
{
cin >> s;
tr.insert(s);
}
tr.build(); tr.solve(m);
return 0;
}
转载请说明出处