求可行方案數,可能容斥,但是操作過於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; }