题解
正难则反,我们可以去计算出不合法的字符串数量,然后用 \(26^m\) 减去不合法的字符串数量即为合法的字符串数量。发现计数时需要维护到枚举到字符串当前位置时的后缀,按照套路,这个东西可以放到AC自动机上来做。先把所有单词丢到AC自动机上,然后设 \(dp[i][p]\) 表示枚举到第 \(i\) 位,且当前后缀为trie树上的根结点到p结点的字符串,此时的不合法字符串总数。我们去枚举第 \(i+1\) 位放置的字符 \(x\),并设 \(T[p][x]\) 为AC自动机拓扑图上从 \(p\) 到达 \(x\) 的结点。那么有 \(dp[i+1][T[p][x]]+=dp[i][p]\)。注意枚举 \(x\) 时不能构造出给出的字符串,需要在构造 \(fail\) 指针时维护一下 \(ed\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define RG register int
#define LL long long
const int MOD=10007;
int ExPow(int b,int n){
int x=1,Power=b%MOD;
while(n){
if(n&1) x=x*Power%MOD;
Power=Power*Power%MOD;
n>>=1;
}
return x;
}
template<size_t _size>
struct AC_automaton{
static const int base=26;
int T[_size][base],fail[_size],ed[_size],deep[_size];
queue<int> Q;
int cnt;
void insert(char *s){
int p=0;
for(RG i=0;s[i];++i){
int x=s[i]-'A';
if(!T[p][x]){T[p][x]=++cnt;deep[cnt]=deep[p]+1;}
p=T[p][x];
}
ed[p]=true;
}
void build(){
for(RG x=0;x<base;++x)
if(T[0][x]) Q.push(T[0][x]);
while(!Q.empty()){
int p=Q.front();Q.pop();
for(RG x=0;x<base;++x){
int v=T[p][x];
if(v){
fail[v]=T[fail[p]][x];Q.push(v);
if(ed[fail[v]]||ed[p]) ed[v]=true;
}
else T[p][x]=T[fail[p]][x];
}
}
}
};
AC_automaton<7000> AC;
char s[105];
int dp[101][6001];
int N,M;
int main(){
scanf("%d%d",&N,&M);
for(RG i=1;i<=N;++i){
scanf("%s",s);
AC.insert(s);
}
AC.build();
dp[0][0]=1;
for(RG i=0;i<M;++i){
for(RG p=0;p<=AC.cnt;++p){
if(AC.ed[p]) continue;
for(RG x=0;x<AC.base;++x){
int v=AC.T[p][x];
if(AC.ed[v]) continue;
dp[i+1][v]+=dp[i][p];
if(dp[i+1][v]>=MOD) dp[i+1][v]%=MOD;
}
}
}
int Ans=0;
for(RG p=0;p<=AC.cnt;++p){
Ans+=dp[M][p];
if(Ans>=MOD) Ans%=MOD;
}
Ans=((ExPow(26,M)-Ans)%MOD+MOD)%MOD;
printf("%d\n",Ans);
return 0;
}