[AC自動機][dp][洛谷P4052][JSOI 2007] 文字生成器
阿新 • • 發佈:2020-10-05
題解
正難則反,我們可以去計算出不合法的字串數量,然後用 \(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\)
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; }