[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]\),所以答案就是:
因为模数小,只有 \(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]密码
祝大家学习愉快!