LGP4052 文本生成器 (AC自动机+dp)
LGP4052 文本生成器
Mean
给定一些模式串,求长度为\(m\)的所有文本串的个数,且该文本串至少包括一个模式串,答案对\(10007\)取模
Sol
AC自动机
对所有模式串建立AC自动机,建出\(trie\)图。
考虑正难则反,求出所有长度为\(m\)的文本串个数,且该文本串不包含任意一个模式串,计其个数为\(cnt\),则答案为\(26^m-cnt\)。
设节点\(x\)为非法点,当且仅当其\(fail\)链上存在至少一个终止点,否则为合法点。
走到非法点\(x\)意味着会至少包含一个模式串。
问题转换成在状态机上走\(m\)步,一共有多少条路径,其路径上全是合法点,求出路径数即为上述\(cnt\)。
利用人人为我的思想,列出\(dp\)方程如下
\(dp[i][j]\)表示在状态机上走\(i\)步,走到状态节点为\(j\)的合法路径数。
\( if(!vis[tr[j][k]]){ dp[i+1][tr[j][k]]=(dp[i+1][tr[j][k]]+dp[i][j])%mod; } \)
\(cnt= \sum_{i=0}^{tot} dp[m][i]\),最后再算上\(26^m\)即可。
Code
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e4+7;
const int N = 10000+10;
const int M = 200;
int tr[N][26],fail[N];
int vis[N];
/**
* AC自动机 + dp
* 求包含若干个给定串中至少一个 的长度为m的字符串的个数
*/
char s[N];
int tot;
int n,m;
int dp[M][N];
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=(1ll*ans*a)%mod;
}
a=(1ll*a*a)%mod;
b>>=1;
}
return ans;
}
void insert(int id){
int p=0;
for(int i=1;s[i];i++){
int v=s[i]-'A';
if(!tr[p][v]){
tr[p][v]=++tot;
}
p=tr[p][v];
}
vis[p]=1;
}
void build(){
queue<int>Q;
for(int i=0;i<26;++i){
if(tr[0][i]){
Q.push(tr[0][i]);
}
}
while(!Q.empty()){
int p = Q.front();
Q.pop();
for(int i=0;i<26;++i){
if(tr[p][i]){
fail[tr[p][i]]=tr[fail[p]][i];
Q.push(tr[p][i]);
vis[tr[p][i]]|=vis[fail[tr[p][i]]];
}
else{
tr[p][i]=tr[fail[p]][i];
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",s+1);
insert(i);
}
build();
dp[0][0]=1;
for(int i=0;i<m;++i){
for(int j=0;j<=tot;++j){
for(int k=0;k<26;++k){
if(!vis[tr[j][k]]){
dp[i+1][tr[j][k]]=(dp[i+1][tr[j][k]]+dp[i][j])%mod;
}
}
}
}
int ans = qpow(26,m);
for(int i=0;i<=tot;++i){
ans = (ans-dp[m][i]+mod)%mod;
}
printf("%d\n",ans);
return 0;
}