题意:
用n个基本词汇拼出长度为L的字符串,每个基本词汇可以用0次,1次或多次,要求拼出的字符串不能出现m个禁忌词汇中的任何一个,求方案数 mod 1e9+7
60% :1<=N,M<=50,L<=100 40%:基本词汇长度不超过2,L<=10
8
^8
8。
思路:
用禁忌词建AC自动机,考虑转移:
L<=100:AC自动机上的DP,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示已经走了长度为
i
i
i的字符串,现在走到了点
j
j
j的方案数。
现在要在后面添加一个基本词汇:如果从
j
j
j开始走会经过一个禁忌词汇的终点,那么这么走是不合法的,无法转移;否则假设从点
j
j
j经过基本词汇
k
k
k走到了点
u
u
u,有转移
d
p
[
i
+
s
i
z
[
k
]
]
[
u
]
+
=
d
p
[
i
]
[
j
]
dp[i+siz[k]][u]+=dp[i][j]
dp[i+siz[k]][u]+=dp[i][j]
L<=10
8
^8
8,基本词汇长度不超过2:
用DP转移有:
d
p
[
i
+
1
]
[
u
]
+
=
d
p
[
i
]
[
j
]
或
d
p
[
i
+
2
]
[
u
]
+
=
d
p
[
i
]
[
j
]
dp[i+1][u]+=dp[i][j]或dp[i+2][u]+=dp[i][j]
dp[i+1][u]+=dp[i][j]或dp[i+2][u]+=dp[i][j]
如果基本词汇长度为1,有
d
p
[
i
+
1
]
[
u
]
+
=
d
p
[
i
]
[
j
]
dp[i+1][u]+=dp[i][j]
dp[i+1][u]+=dp[i][j],此时可用矩阵快速幂优化。
长度为2时,给每一个点加一个虚点,给
j
j
j到
j
1
j_1
j1,
j
1
j_1
j1到
u
u
u连边,
相当于
d
p
[
i
+
1
]
[
j
1
]
+
=
d
p
[
i
]
[
j
]
,
d
p
[
i
+
2
]
[
u
]
+
=
d
p
[
i
+
1
]
[
j
1
]
dp[i+1][j_1]+=dp[i][j],dp[i+2][u]+=dp[i+1][j_1]
dp[i+1][j1]+=dp[i][j],dp[i+2][u]+=dp[i+1][j1]。
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 110
#define MO 1000000007
inline void Add(int &a,int b)
{
a+=b;
a%=MO;
if(a<0) a+=MO;
}
int dp[MAXN][MAXN],len[MAXN],Fail[MAXN],ch[MAXN][26],ed[MAXN],trans[MAXN][MAXN],tot,n,m,L,N;
char s[MAXN][MAXN];
struct Mat
{
int a[MAXN*2][MAXN*2];
Mat operator *(const Mat &A)
{
Mat c;memset(c.a,0,sizeof c.a);
for(int i=0;i<=N;i++)
for(int j=0;j<=N;j++)
for(int k=0;k<=N;k++)
Add(c.a[i][j],1LL*a[i][k]*A.a[k][j]%MO);
return c;
}
};
void Insert(int id)
{
int len=strlen(s[id]),nw=0;
for(int i=0;i<len;i++)
{
int c=s[id][i]-'a';
if(!ch[nw][c]) ch[nw][c]=++tot;
nw=ch[nw][c];
}
ed[nw]=1;
}
queue<int>Q;
void Build()
{
for(int i=0;i<26;i++) if(ch[0][i]) Q.push(ch[0][i]);
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=0;i<26;i++)
if(!ch[u][i]) ch[u][i]=ch[Fail[u]][i];
else
{
int v=ch[u][i];
Fail[v]=ch[Fail[u]][i];
ed[v]|=ed[Fail[v]];
Q.push(v);
}
}
}
void Pre(int id)
{
len[id]=strlen(s[id]);
for(int i=0;i<len[id];i++) s[id][i]-='a';
for(int i=0;i<=tot;i++)
if(!ed[i])
{
int nw=i;
for(int j=0;j<len[id];j++)
{
nw=ch[nw][(int)s[id][j]];
if(ed[nw])
{
nw=-1;
break;
}
}
trans[i][id]=nw;
}
}
void Solve1()
{
dp[0][0]=1;
for(int i=0;i<L;i++)
for(int j=0;j<=tot;j++)
if(dp[i][j]&&!ed[j])
{
for(int k=0;k<n;k++)
if(trans[j][k]!=-1&&i+len[k]<=L)
Add(dp[i+len[k]][trans[j][k]],dp[i][j]);
}
int ans=0;
for(int j=0;j<=tot;j++)
Add(ans,dp[L][j]);
printf("%d\n",ans);
}
void Solve2()
{
Mat ret,a;
memset(ret.a,0,sizeof ret.a),memset(a.a,0,sizeof a.a);
N=tot*2+1;
for(int i=0;i<=N;i++) ret.a[i][i]=1;
for(int i=0;i<=tot;i++)
if(!ed[i])
{
for(int j=0;j<n;j++)
if(trans[i][j]!=-1)
{
if(len[j]==1) a.a[i][trans[i][j]]++;
else a.a[i+tot+1][trans[i][j]]++;
}
}
for(int i=0;i<=tot;i++) a.a[i][i+tot+1]++;
while(L)
{
if(L&1) ret=ret*a;
a=a*a;
L>>=1;
}
int ans=0;
for(int i=0;i<=tot;i++)
Add(ans,ret.a[0][i]);
printf("%d\n",ans);
}
int main()
{
scanf("%d%d%d",&n,&m,&L);
for(int i=0;i<n;i++)
scanf("%s",s[i]);
for(int i=0;i<m;i++)
{
scanf("%s",s[n]);
Insert(n);
}
Build();
memset(trans,-1,sizeof trans);
for(int i=0;i<n;i++)
Pre(i);
if(L<=100)
Solve1();
else Solve2();
}