题解文本生成器

[JSOI2007]文本生成器

古老的博文。

这题就是那种著名的AC自动机+dp题,算法唯一,如果你不会AC自动机,出门左转[传送门]

这题当中的那些单词就相当于AC自动机模板中的模式串,只不过这题当中是要求满足“包含至少一个模式串的”文本串而已。

所以先把模式串放到 \(\texttt{trie}\) 树上,然后用AC自动机的 \(\texttt{build()}\) 函数把 \(fail\) 指针构造出来,并把 \(ch[][]\) 数组进化为一条永通路,以便避免文本串匹配时一个模式串到结尾了没有地方可走的尴尬。

以上都是对模板的解说。 对于这题,我们发现“包含至少一个”这样的约束条件,就想到 答案\(=\)整体\(-\)剩余,先求出一个模式串都不包含的文本串数,然后求出没有约束条件下文本串有 \(26^m\) 种,就可以得出答案。

因为要求一个模式串都不包含的文本串数,所以 \(\texttt{insert(s)}\) 的时候在有字符串结尾的节点 \(x\) 上注 \(mk[x]=1\),如下:

void insert(char*s){
	int n=strlen(s+1),p=1;
	for(int i=1;i<=n;i++){
		int c=s[i]-'A'+1;
		if(!ch[p][c]) ch[p][c]=++cnt;
		p=ch[p][c];
	}
	mk[p]=1;
}

又因为如果一个字符串的后缀是模式串,那么它也不能出现在文本串中,所以在构造AC自动机 \(fail\) 指针的时候要令 \(mk[x]|=mk[fail[x]]\),如下:

void build(){
	for(int i=1;i<=26;i++) ch[0][i]=1;
	queue<int> q;while(q.size()) q.pop();q.push(1);
	while(q.size()){
		int x=q.front();q.pop();
		for(int c=1;c<=26;c++)
			if(ch[x][c]) fa[ch[x][c]]=ch[fa[x]][c],mk[ch[x][c]]
				|=mk[fa[ch[x][c]]],q.push(ch[x][c]);
			else ch[x][c]=ch[fa[x]][c];
	}
}

因为这题的文本串没有实体,不能通过AC自动机上 \(fail\) 指针一跳完事,所以根据AC自动机dp。\(dp[i][j]\) 表示长度为 \(i\) 且后缀字符为AC自动机上节点 \(j\) 的文本串数量,又文本串中不能出现模式串,所以有转移方程:

for(int i=1;i<=m;i++)
	for(int j=1;j<=t.cnt;j++)//全点参与
		for(int c=1;c<=26;c++) //沿着子节点生成文本串下一个字母
			if(!t.mk[t.ch[j][c]])  //避开模式串
				dp[i][t.ch[j][c]]=(dp[i][t.ch[j][c]]+dp[i-1][j])%mod;

如果AC自动机节点数为 \(cnt\),那么一个模式串都不包含的文本串数就为 \(\sum\limits_{i=1}^{cnt}dp[m][i]\),所以答案就是:

\[(26^m-\sum\limits_{i=1}^{cnt}dp[m][i])\mod 10007 \]

因为模数小,只有 \(10007\),所以不必开 \(\texttt{long long}\),但如果你忘记取模了,\(\texttt{long long}\) 也救不了你。如果你懂了,那么蒟蒻就放代码了:

#include <bits/stdc++.h>
using namespace std;
const int N=70;
const int M=110;
const int mod=1e4+7;
class Trie{
public:
	int ch[N*M][30],cnt;
	bool mk[N*M];
	Trie(){cnt=1;}
	void insert(char*s){
		int n=strlen(s+1),p=1;
		for(int i=1;i<=n;i++){
			int c=s[i]-'A'+1;
			if(!ch[p][c]) ch[p][c]=++cnt;
			p=ch[p][c];
		}
		mk[p]=1;
	}
};
class Acam:public Trie{
public:
	int fa[N*M];
	void build(){
		for(int i=1;i<=26;i++) ch[0][i]=1;
		queue<int> q;while(q.size()) q.pop();q.push(1);
		while(q.size()){
			int x=q.front();q.pop();
			for(int c=1;c<=26;c++)
				if(ch[x][c]) fa[ch[x][c]]=ch[fa[x]][c],mk[ch[x][c]]
					|=mk[fa[ch[x][c]]],q.push(ch[x][c]);
				else ch[x][c]=ch[fa[x]][c];
		}
	}
}t;
int n,m,dp[M][N*M],ans=1;
char s[M];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%s",s+1),t.insert(s);
	t.build();
	dp[0][1]=1;
	for(int i=1;i<=m;i++) for(int j=1;j<=t.cnt;j++)
		for(int c=1;c<=26;c++) if(!t.mk[t.ch[j][c]]) 
			dp[i][t.ch[j][c]]=(dp[i][t.ch[j][c]]+dp[i-1][j])%mod;
	for(int i=m,j=26;i;j=(j*j)%mod,i>>=1)
		if(i&1) ans=(ans*j)%mod;
	for(int i=1;i<=t.cnt;i++) ans=(ans-dp[m][i]+mod)%mod;
	printf("%d\n",ans);
	return 0;
}

后续学习推荐题目:

[NOI2011]阿狸的打字机
[SDOI2014]数数
[JSOI2009]密码

祝大家学习愉快!

原文地址:https://www.cnblogs.com/George1123/p/12444585.html