「LuoguP3808」 【模板】AC自动机(简单版)
题目背景
通过套取数据而直接“打表”过题者,是作弊行为,发现即棕名。
这是一道简单的AC自动机模板题。
用于检测正确性以及算法常数。
为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交。
管理员提示:本题数据内有重复的单词,且重复单词应该计算多次,请各位注意
题目描述
给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。
输入输出格式
输入格式:第一行一个n,表示模式串个数;
下面n行每行一个模式串;
下面一行一个文本串。
输出格式:一个数表示答案
输入输出样例
说明
subtask1[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6,n=1;
subtask2[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6;
题解
在通常就没什么人认真听的集训里,qwerta刚撸完一把Win7原装的扫雷,舒适的抬起头,正好对上了老师讲fail树的眼神。
所以是懂一丢丢手推方法的。
建议大家先构造点小数据手推,像是什么在$her,him,he,his,she$上匹配$shehisher$之类的。
网上教程也蛮多,就不画图了。(懒
其实是自己也推不蛮清楚
然后是教材QAQ yyb大佬的博客
总之,AC自动机就是在trie树上bfs一下然后乱搞,可以理解为trie上的KMP叭。
自己都不怎么会,就不讲这个东西了QAQ
1 #include<queue> 2 #include<cstdio> 3 #include<cstring> 4 #include<iostream> 5 using namespace std; 6 #define R register 7 const int MAXL=1e6+3; 8 char s[MAXL]; 9 struct emm{ 10 int fail; 11 int nxt[26]; 12 int tag; 13 }AC[MAXL];//Tree结构体 14 queue<int>q;//get_fail的时候用的queue 15 int main() 16 { 17 //freopen("a.in","r",stdin); 18 ios::sync_with_stdio(false); 19 cin.tie(false);cout.tie(false); 20 int n; 21 cin>>n; 22 int tot=0; 23 //trie 24 while(n--) 25 { 26 cin>>s; 27 int len=strlen(s); 28 int now=0;//now表示当前的节点编号 29 for(R int i=0;i<len;++i) 30 { 31 if(!AC[now].nxt[s[i]-'a'])//如果当前节点没有这个儿子 32 AC[now].nxt[s[i]-'a']=++tot;//就造个儿子 33 now=AC[now].nxt[s[i]-'a'];//now跳到这个儿子上 34 } 35 AC[now].tag++;//now最后在的地方是这个模式串的结束点,标记一下 36 } 37 //get_fail 38 { 39 //首先把跟0号节点相连的点处理一下 40 for(R int i=0;i<26;++i) 41 { 42 if(AC[0].nxt[i])//如果这个儿子存在 43 { 44 AC[AC[0].nxt[i]].fail=0;//把fail指向0号节点(其实默认就是0啊QAQ 45 q.push(AC[0].nxt[i]);//把这个儿子push进去 46 } 47 } 48 while(!q.empty()) 49 { 50 int x=q.front();q.pop();//取点 51 for(R int i=0;i<26;++i) 52 { 53 if(AC[x].nxt[i])//如果这个儿子存在 54 { 55 AC[AC[x].nxt[i]].fail=AC[AC[x].fail].nxt[i];//敲重点!!! 56 //这个儿子的fail,等于这个节点fail的儿子 会手推就能懂叭QAQ 57 q.push(AC[x].nxt[i]);//push进去 58 } 59 else 60 AC[x].nxt[i]=AC[AC[x].fail].nxt[i];//这里直接把nxt指向了fail的对应儿子 61 } 62 } 63 } 64 //run 65 cin>>s; 66 long long ans=0; 67 int len=strlen(s); 68 int now=0;//now表示当前节点编号 69 for(R int i=0;i<len;++i) 70 { 71 now=AC[now].nxt[s[i]-'a'];//往下走一层(因为之前直接把空的nxt往fail指了,所以不需要通常的while跳fail 72 //大概就是Trie图和Trie树的区别了叭 73 for(R int t=now;t&&AC[t].tag!=-1;t=AC[t].fail)//从这个点开始暴力跳fail,找匹配 74 { 75 ans+=AC[t].tag; 76 AC[t].tag=-1;//这是个剪枝 77 } 78 } 79 cout<<ans; 80 return 0;//撒花 81 }
然后吐槽一下咕咕的数据,之前有一大段把$now$和$i$混用了,结果还过了一个点(???