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;
}
posted @ 2021-10-18 16:37  Qquun  阅读(53)  评论(0)    收藏  举报