Description
Chandra 是一个魔法天才。
从一岁时接受火之教会洗礼之后,Chandra 就显示出对火元素无与伦比的亲和力,轻而易举地学会种种晦涩难解的法术。这也多亏 Chandra 有着常人难以企及的语言天赋,让她能轻松流利地说出咒语中那些极其拗口的魔法词汇。
直到十四岁,开始学习威力强大的禁咒法术时,Chandra 才遇到了障碍。
根据火之魔法规则,禁咒的构成单位是 N 个基本词汇。施法时只要凝聚精神力,说出一段用这些词语组成的长度恰好等于 L 的语言,就能释放威力超乎想象的火法术。过去的魔法师们总结了几种表达起来最连贯的组合方式,方便施法者以最快语速完成法术。
但具有魔法和语言双重天才的 Chandra 不满足于这几种流传下来的禁咒,因为她可以毫无困难地说出普通人几乎不可能表达的禁咒语句。然而,在实际施法时,Chandra 发现有些自创禁咒念出后不但没有预期效果,反而会使自己的精神力迅速枯竭,十分难受。
这个问题令 Chandra 万分不解。她大量阅读典籍,到处走访魔法学者,并且不顾精神折磨一次又一次尝试新咒语,希望找出问题的答案。
很多年过去了,在一次远古遗迹探险中,Chandra 意外闯进了火之神艾利克斯的不知名神殿。根据岩土特征分析,神殿应该有上万年的历史,这是极其罕见的。Chandra 小心翼翼地四处探索,沿着魔力流动来到一间密室。她看见密室中央悬浮着一本书籍。在魔法保护下书籍状况完好。精通上古语言的 Chandra 读过此书,终于解开了多年的困惑。
禁咒法术之所以威力强大,是因为咒语借用了火之神艾利克斯的神力。这本书里记载了艾利克斯生平忌讳的 M 个词语,比如情敌的名字、讨厌的植物等等。使用禁咒法术时,如果语言中含有任何忌讳词语,就会触怒神力而失效,施法者也一并遭受惩罚。
例如,若 ”banana” 是唯一的忌讳词语,“an”、”ban”、”analysis” 是基本词汇,禁咒长度须是 11,则“bananalysis” 是无效法术,”analysisban”、”anbanbanban”是两个有效法术。注意:一个基本词汇在禁咒法术中可以出现零次、一次或多次;只要组成方式不同就认为是不同的禁咒法术,即使书写形式相同。
谜题破解,Chandra 心情大好。她决定计算一共有多少种有效的禁咒法术。
由于答案可能很大,你只需要输出答案模 1,000,000,007 的结果。
Input
第一行,三个正整数 N, M, L。
接下来 N 行,每行一个只含小写英文字母的字符串,表示一个基本词汇。
接下来 M 行,每行一个只含小写英文字母的字符串,表示一个忌讳词语。
对于60%的数据1<=N,M<=50,L<=100
对于另40%数据基本词汇长度不超过2,L<=10^8
Output
仅一行,一个整数,表示答案(模 10^9+7)。
Sample Input
4 2 10
boom
oo
ooh
bang
ob
mo
Sample Output
14
【样例解释 】
有效的禁咒法术共有 14 种:boom/bang/oo,oo/oo/oo/oo/oo,oo/oo/ooh/ooh,
oo/ooh/oo/ooh, oo/ooh/ooh/oo, ooh/oo/oo/ooh, ooh/oo/ooh/oo,
ooh/ooh/boom, ooh/ooh/oo/oo, ooh/ooh/bang, ooh/bang/ooh,
bang/oo/oo/oo, bang/ooh/ooh, bang/bang/oo。
分析:
很显然,这道题就是AC自动机上的DP
当数据小的时候,我们可以直接DP;数据大我们就用矩阵加速
一开始
我把所有的基本串建了AC自动机
在自动机上跑一遍,找到其中是禁忌串的结点(这些结点是不能到达的)
设计状态:f[i][j],表示第i位,到了AC自动机第j个结点
但是转移的时候遇到了问题:不能保证出现完整的基本串
以前我们加了一维解决这个问题
但是这道题的数据范围太大了,我们不能这样解决问题
正解 . L<=100
网上的解法都是把禁忌串扔到一棵AC自动机上
在AC自动机上跑一遍,记录一个数组:
to[i][j],表示从自动机上的结点i开始,能转移到第j个基本串(在AC自动机上的位置是第to[i][j]个结点)
设计状态:f[i][j],表示第i位,到了AC自动机第j个结点
咦,这个状态不是和以前一样吗
是啊,但是我们之前预处理出了一个to数组,这就有很大的作用了
状态转移:
- 第一维枚举长度i
- 第二维枚举AC自动机上的结点j
- 第三维枚举to[j][k],这样我们转移的时候我们直接转移一整个字符串,这样就能解决我们一开始的问题:保证出现完整的基本串
正解 . L>100
我们还是处理出这个神奇的to数组
我们构造一个2top*2top的矩阵H[i][j],表示AC自动机上的结点i,经过某个基本串后,能够转移到第j个结点
- 第一维枚举AC自动机上的结点i
- 第二维枚举to[i][j]
- 如果基本串的长度为1,H[i][to[i][j]]++
- 如果基本串的长度为2,H[i+top+1][to[i][j]]++
Q.
说实话我不太理解为什么要用禁忌串建AC自动机
tip
数组开小了,WA了好长时间。。。
状态不好,代码写的不好↓
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define ll long long
using namespace std;
const int N=110;
const ll p=1e9+7;
int n,m,L,ch[N][27],top=0,fail[N],to[N][N],la[N],lb[N],mn;
char basic[N][N],avoid[N][N];
bool ed[N];
ll f[N][N];
struct node{
ll H[210][210];
node operator *(const node &a)const
{
node ans;
for (int i=0;i<=mn;i++)
for (int j=0;j<=mn;j++)
{
ans.H[i][j]=0;
for (int k=0;k<=mn;k++)
ans.H[i][j]=(ans.H[i][j]+(ll)H[i][k]*a.H[k][j])%p;
}
return ans;
}
void clear()
{
memset(H,0,sizeof(H));
}
node KSM(int b)
{
node ans=(*this),a=(*this);
b--;
while (b)
{
if (b&1) ans=ans*a;
b>>=1;
a=a*a;
}
return ans;
}
};
void build(char s[])
{
int now=0;
for (int i=1;i<=strlen(s+1);i++)
{
int x=s[i]-'a';
if (!ch[now][x]) ch[now][x]=++top;
now=ch[now][x];
}
ed[now]=1;
}
void makefail()
{
queue<int> q;
for (int i=0;i<26;i++)
if (ch[0][i]) q.push(ch[0][i]);
while (!q.empty())
{
int now=q.front(); q.pop();
for (int i=0;i<26;i++)
{
if (!ch[now][i])
{
ch[now][i]=ch[fail[now]][i];
continue;
}
fail[ch[now][i]]=ch[fail[now]][i];
ed[ch[now][i]]|=ed[fail[ch[now][i]]];
q.push(ch[now][i]);
}
}
}
void solve1()
{
f[0][0]=1;
for (int i=0;i<L;i++)
for (int j=0;j<=top;j++) if (f[i][j])
for (int k=1;k<=n;k++) if (to[j][k]!=-1&&lb[k]+i<=L)
f[i+lb[k]][to[j][k]]=(f[i+lb[k]][to[j][k]]+f[i][j])%p;
ll ans=0;
for (int i=0;i<=top;i++) ans=(f[L][i]+ans)%p;
printf("%lld\n",ans);
}
void solve2()
{
node H;
mn=top*2+1;
for (int i=0;i<=top;i++)
{
for (int j=1;j<=n;j++)
if (to[i][j]!=-1)
if (strlen(basic[j]+1)==1)
H.H[i][to[i][j]]++;
else
H.H[i+top+1][to[i][j]]++;
H.H[i][i+top+1]++;
}
node an=H.KSM(L);
ll ans=0;
for (int i=0;i<=top;i++)
ans=(ans+an.H[0][i])%p;
printf("%lld\n",ans);
}
int main()
{
scanf("%d%d%d",&n,&m,&L);
for (int i=1;i<=n;i++)
scanf("%s",basic[i]+1),lb[i]=strlen(basic[i]+1);
for (int i=1;i<=m;i++)
scanf("%s",avoid[i]+1),la[i]=strlen(avoid[i]+1);
for (int i=1;i<=m;i++) build(avoid[i]);
makefail();
for (int i=0;i<=top;i++)
for (int j=1;j<=n;j++)
{
int now=i,k=1,len=strlen(basic[j]+1);
for (;k<=len&&!ed[now];now=ch[now][basic[j][k]-'a'],k++);
if (ed[now]) to[i][j]=-1;
else if (k==len+1) to[i][j]=now;
else to[i][j]=-1;
}
if (L<=100) solve1();
else solve2();
return 0;
}