版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/82992786
BZOJ传送门
洛谷传送门
解析:
为什么 又是关键字啊啊啊啊!!!
思路:
不要试图用容斥原理来做这道题。。。
这就是一个 自动机上的
首先建出 自动机,这时候我们得到所有串之间的包含关系。
然后,如果您有直接统计答案的做法,请私信博主,方便完善题解,这里提供一种反向统计的方法。
我们不统计满足条件的方案数,我们统计不满足的方案数。
因为总方案十分好算,就是 ,所以满足条件的方案数就是不满足的补集。
那么考虑如下
:
其中
是当前考虑的前缀字符串的长度,
是所有能够转移到
的自动机结点集合。
这个
十分显然,我们现在考虑怎么处理不合法的方案。
考虑我们不能让任何一个
自动机中已经插入的字符串出现在方案中,我们只需要记录结点是否是某个字符串的结尾。每次
之前跳
链检验一下就行了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
#define end End
cs int mod=10007;
cs int N=6005;
int son[N][26],dp[104][N],fail[N],end[N];
int tot=1,n,m;
inline void insert(char c[]){
int len=strlen(c),now=1;
for(int re i=0;i<len;++i){
int x=c[i]-'A';
if(!son[now][x])son[now][x]=++tot;
now=son[now][x];
}
end[now]=true;
}
inline void build_ACauto(){
queue<int> q;
q.push(1);
while(!q.empty()){
int now=q.front();
q.pop();
for(int re i=0;i<26;++i){
if(son[now][i]){
int tmp=fail[now];
while(!son[tmp][i])tmp=fail[tmp];
fail[son[now][i]]=son[tmp][i];
q.push(son[now][i]);
}
}
}
}
inline int quickpow(int a,int b){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
char c[101];
signed main(){
for(int re i=0;i<26;++i)son[0][i]=1;
cin>>n>>m;
for(int re i=1;i<=n;++i){
scanf("%s",c);
insert(c);
}
build_ACauto();
dp[0][1]=1;
for(int re k=0;k<m;++k){
for(int re j=1;j<=tot;++j){
for(int re i=0;i<26;++i){
int now=j;
bool flag=true;
while(now){
if(end[son[now][i]]){
flag=false;
break;
}
now=fail[now];
}
if(!flag)continue;
now=j;
while(!son[now][i])now=fail[now];
now=son[now][i];
dp[k+1][now]+=dp[k][j];
dp[k+1][now]%=mod;
}
}
}
int ans=0;
for(int re i=1;i<=tot;++i)ans=(ans+dp[m][i])%mod;
cout<<(quickpow(26,m)-ans+mod)%mod;
return 0;
}