求可行方案数,可能容斥,但是操作过于complex,复杂度爆炸,不可做。
由于总方案数一定,为26^m,求不可行方案数,相减即可。此时的不可行方案数模型为求使一个字符串不含任何单词的方案数。
那么我们定义dp[i][j],表示走i步(即路径长度为i),到达Ac_automaton上第j个节点的方案数(应该是小套路),那么我们先给出没有细节限制的状态转移方程:
dp[i][k]+=dp[i-1][j];(k是j的儿子节点)
那么我们想一下实现细节,首先我们的方程应该是不经过任何单词的方案数,所以在单词尾打标记是一定的,同时根据以往Ac_automaton的经验,这个单词被标记,则fail指向它的那些节点也应该被标记,因为该单词是其他单词的后缀,一旦其他单词出现,则该单词一定出现,不符合转移要求,所以要打上标记。
然后在上述方程中,我们计算贡献时是不能经过任何单词的,所以一旦枚举到有标记的节点直接continue掉。
最后统计答案时,有标记的节点不应该作出贡献,可能有同学会问,我上面不是没更新有标记节点吗?他们不是0?实际上,我们只是没让这些节点去作出贡献,而有的贡献已经转移到他们身上(自己去观察一下方程)。
ans=26^m-sigma(dp[m][i])。
不要忘了取模法则。+个mod防负数。
#include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<cstdio> #include<vector> #include<queue> #include<map> using namespace std; const int mod=10007; int n,m; char s[200]; struct Ac_automaton{ int tot,root,ans,cnt; int trie[16000][26],fail[16000],v[16000]; int dp[200][16000]; void clear(){tot=root=ans=1;cnt=0;} void insert(){ int l=strlen(s+1); int now=root; for(int i=1;i<=l;i++){ if(!trie[now][s[i]-'A']) trie[now][s[i]-'A']=++tot; now=trie[now][s[i]-'A']; }v[now]=1; } void generate(){ queue<int>q; for(int i=0;i<26;i++) if(trie[root][i]){ fail[trie[root][i]]=root; q.push(trie[root][i]); }else trie[root][i]=root; while(!q.empty()){ int now=q.front(); q.pop(); for(int i=0;i<26;i++) if(trie[now][i]){ fail[trie[now][i]]=trie[fail[now]][i]; v[trie[now][i]]|=v[fail[trie[now][i]]]; q.push(trie[now][i]); }else trie[now][i]=trie[fail[now]][i]; } } int Dp(){ dp[0][1]=1; for(int i=1;i<=m;i++) for(int j=1;j<=tot;j++){ if(v[j]) continue; for(int k=0;k<26;k++) dp[i][trie[j][k]]=(dp[i][trie[j][k]]+dp[i-1][j])%mod; } for(int i=1;i<=tot;i++) if(!v[i]) cnt=(cnt+dp[m][i])%mod; for(int i=1;i<=m;i++) ans=(ans*26)%mod; return (ans%mod-cnt%mod+mod)%mod; } }Acm; int main(){ scanf("%d%d",&n,&m); Acm.clear(); for(int i=1;i<=n;i++){ scanf("%s",s+1); Acm.insert(); }Acm.generate(); printf("%d",Acm.Dp()); return 0; }