• 题解文本生成器


    [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]密码

    祝大家学习愉快!

  • 相关阅读:
    【转】人类的心理行为模式----《影响力》笔记
    c++类型形参的实参的受限转换
    如何确定网页和查询的相关性
    英语美文摘要
    特殊的字符数组
    c++中的虚函数
    [C#] 常用工具类——应用程序属性信息访问类
    c#常用工具类:文件和二进制转换
    [C#] 常用工具类——文件操作类
    Lamda和Linq语法对比详细
  • 原文地址:https://www.cnblogs.com/George1123/p/12444585.html
Copyright © 2020-2023  润新知