AC自动机果断是神一样的东西,我赶在比赛前学习还是有用的,AC自动机最基本的是可以解决多个模式串在一个长字符串中出现的种类数或次数:
我暂时还是修改大神们的模板的昂
满满个人注释版帮助自己理解版:
1 //该程序不能判别相同模式串,因此若模式串重复,答案会将相同模式串当做不同的处理,因此若需要可以用map去重或修改insert
2 #include<stdio.h>
3 #include<string.h>
4 #include<queue>
5 using namespace std; 6 const int maxm=500006; //maxm是总结点数:约为字母数+++
7
8 char s[1000005],word[55]; 9 int nxt[maxm][26],tail[maxm],f[maxm],size; //nxt是结点指向不同字母的结点下标,tail是表示该结点为几个单词的词尾(可能需要计算重复的模式串情况),f是当不匹配时转跳到的结点下标,size是结点数
10
11 int newnode(){ //初始化整个trie或建立新的结点时,首先初始化当前结点所指向的26个字母的结点为0,表示暂时还没有指向的字母,然后暂定该结点不是单词尾结点,暂无失配时转跳位置(即转跳到根节点),返回结点标号
12 memset(nxt[size],0,sizeof(nxt[size])); 13 f[size]=tail[size]=0; 14 return size++; 15 } 16
17 void insert(char s[]){ //构造trie,p为当前结点的上一个结点标号,初始为0;x即为当前结点(上个结点标号指向当前字母的结点)标号,若此结点还未出现过,那么就建立这个结点;然后更新p为当前结点标号以便后续操作
18 int i,p=0; 19 for(i=0;s[i];i++){ 20 int &x=nxt[p][s[i]-'a']; 21 p=x?x:x=newnode(); 22 } 23 tail[p]++; //此时仅将s串记录,即将s串结尾的结点加1,若无相同模式串,则此操作只会使所有串尾结点的tail值由0变为1,但有相同模式串,则会重复记录,需要去重可以用map或用tail[p]=1;语句来完成
24 } 25
26 void makenxt(){ //利用bfs来构造失配指针
27 int i; 28 queue<int>q; 29 f[0]=0; //先将0结点挂的字母加入队列,失配指针指向0结点
30 for(i=0;i<26;i++){ 31 int v=nxt[0][i]; 32 if(v){ 33 f[v]=0; 34 q.push(v); 35 } 36 } 37 while(!q.empty()){ 38 int u=q.front(); 39 q.pop(); 40 for(i=0;i<26;i++){ 41 int v=nxt[u][i]; 42 if(!v)nxt[u][i]=nxt[f[u]][i]; //当u结点没有i对应字母,则视为失配,将其指向失配后转跳到的结点所指向的i对应字母
43 else q.push(v); //u结点存在指向i的结点,则将所指向的结点下标加入队列
44 f[v]=nxt[f[u]][i]; //失配指针指向上个结点失配指针指向结点所挂当前字母的结点
45 } 46 } 47 } 48
49 int query(char s[]){ //查询s串中模式串出现了多少种/次
50 int ans=0,v=0; 51 for(int i=0;s[i];i++){ 52 while(v&&!nxt[v][s[i]-'a'])v=f[v]; //先匹配直到没有失配
53 v=nxt[v][s[i]-'a']; 54 int tmp=v; 55 while(tmp){ 56 ans+=tail[tmp]; 57 tail[tmp]=0; //这里加这句是为了仅计算出现多少种模式链,而若不加这句则可以计算累计出现多少次
58 tmp=f[tmp]; 59 } 60 } 61 return ans; 62 } 63
64 int main(){ 65 int T; 66 scanf("%d",&T); 67 while(T--){ 68 int n; 69 scanf("%d",&n); 70 size=0,newnode(); 71 for(int i=0;i<n;i++){ 72 scanf("%s",word); 73 insert(word); 74 } 75 makenxt(); 76 scanf("%s",s); 77 printf("%d\n",query(s)); 78 } 79 return 0; 80 }
另:加上last数组的版本,last数组可以很大程度上缩短时间,但是也同样要考虑卡空间的问题,另外鹏神也说last比较适用于纯匹配问题,可能套算法的话用处不是非常大:
1 // 有nxt数组版本。。该程序不能判别相同模式串,因此若模式串重复,答案会将相同模式串当做不同的处理,因此若需要可以用map去重或修改insert
2 #include<stdio.h>
3 #include<string.h>
4 #include<queue>
5 using namespace std; 6 const int maxm=500006; //maxm是总结点数:约为字母数+++
7
8 char s[1000005],word[55]; 9 int nxt[maxm][26],tail[maxm],f[maxm],size; //nxt是结点指向不同字母的结点下标,tail是表示该结点为几个单词的词尾(可能需要计算重复的模式串情况),f是当不匹配时转跳到的结点下标,size是结点数
10 int last[maxm]; //last指针是指向上一个是单词结尾的结点,由于是由失配指针拓展得到的,因此所指向的单词都是该结点表示的单词的后缀单词,但由于可能卡空间,所以虽然可以在时间上优化,但是有时并不使用
11
12 int newnode(){ //初始化整个trie或建立新的结点时,首先初始化当前结点所指向的26个字母的结点为0,表示暂时还没有指向的字母,然后暂定该结点不是单词尾结点,暂无失配时转跳位置(即转跳到根节点),返回结点标号
13 memset(nxt[size],0,sizeof(nxt[size])); 14 f[size]=tail[size]=0; 15 return size++; 16 } 17
18 void insert(char s[]){ //构造trie,p为当前结点的上一个结点标号,初始为0;x即为当前结点(上个结点标号指向当前字母的结点)标号,若此结点还未出现过,那么就建立这个结点;然后更新p为当前结点标号以便后续操作
19 int i,p=0; 20 for(i=0;s[i];i++){ 21 int &x=nxt[p][s[i]-'a']; 22 p=x?x:x=newnode(); 23 } 24 tail[p]++; //此时仅将s串记录,即将s串结尾的结点加1,若无相同模式串,则此操作只会使所有串尾结点的tail值由0变为1,但有相同模式串,则会重复记录,需要去重可以用map或用tail[p]=1;语句来完成
25 } 26
27 void makenxt(){ //利用bfs来构造失配指针
28 int i; 29 queue<int>q; 30 f[0]=0; 31 for(i=0;i<26;i++){ //首先将0结点(根节点)连接的字母结点加入队列,并定失配指针和last指针都指向0结点
32 int v=nxt[0][i]; 33 if(v){ 34 f[v]=last[v]=0; 35 q.push(v); 36 } 37 } 38 while(!q.empty()){ 39 int u=q.front(); 40 q.pop(); 41 for(i=0;i<26;i++){ 42 int v=nxt[u][i]; 43 if(!v)nxt[u][i]=nxt[f[u]][i]; //当u结点没有i对应字母,则视为失配,将其指向失配后转跳到的结点所指向的i对应字母
44 else q.push(v); //u结点存在指向i的结点,则将所指向的结点下标加入队列
45 f[v]=nxt[f[u]][i]; //设置这个结点的失配指针指向上个结点失配后的指向字母i的结点,由于bfs一定会从字典树浅层到深层,即从字符串短到长,而失配转跳后表示的字符串长度严格减少,所以只需要指向一次即可
46 last[v]=tail[f[v]]?f[v]:last[f[v]]; //若失配指针指向的结点是单词结尾,那么当前结点失配后就可以直接指向失配结点,即失配路径上的上一个单词结点,若失配结点不是单词结尾,就指向失配结点的last
47 } 48 } 49 } 50
51 int query(char s[]){ //查询s串中模式串出现了多少种/次
52 int ans=0,v=0; 53 for(int i=0;s[i];i++){ 54 while(v&&!nxt[v][s[i]-'a'])v=f[v]; 55 v=nxt[v][s[i]-'a']; 56 int tmp=v; 57 while(tmp){ 58 ans+=tail[tmp]; 59 tail[tmp]=0; //这里加这句是为了仅计算出现多少种模式链,而若不加这句则可以计算累计出现多少次
60 tmp=last[tmp]; 61 } 62 } 63 return ans; 64 } 65
66 int main(){ 67 int T; 68 scanf("%d",&T); 69 while(T--){ 70 int n; 71 scanf("%d",&n); 72 size=0,newnode(); 73 for(int i=0;i<n;i++){ 74 scanf("%s",word); 75 insert(word); 76 } 77 makenxt(); 78 scanf("%s",s); 79 printf("%d\n",query(s)); 80 } 81 return 0; 82 }
并没有注释复制可以用用用版:
1 #include<stdio.h>
2 #include<string.h>
3 #include<queue>
4 using namespace std; 5 const int maxm=500006; 6
7 char s[1000005],word[55]; 8 int nxt[maxm][26],tail[maxm],f[maxm],size; 9 int last[maxm]; 10
11 int newnode(){ 12 memset(nxt[size],0,sizeof(nxt[size])); 13 f[size]=tail[size]=0; 14 return size++; 15 } 16
17 void insert(char s[]){ 18 int i,p=0; 19 for(i=0;s[i];i++){ 20 int &x=nxt[p][s[i]-'a']; 21 p=x?x:x=newnode(); 22 } 23 tail[p]++; 24 } 25
26 void makenxt(){ 27 int i; 28 queue<int>q; 29 f[0]=0; 30 for(i=0;i<26;i++){ 31 int v=nxt[0][i]; 32 if(v){ 33 f[v]=last[v]=0; 34 q.push(v); 35 } 36 } 37 while(!q.empty()){ 38 int u=q.front(); 39 q.pop(); 40 for(i=0;i<26;i++){ 41 int v=nxt[u][i]; 42 if(!v)nxt[u][i]=nxt[f[u]][i]; 43 else q.push(v); 44 f[v]=nxt[f[u]][i]; 45 last[v]=tail[f[v]]?f[v]:last[f[v]]; 46 } 47 } 48 } 49
50 int query(char s[]){ 51 int ans=0,v=0; 52 for(int i=0;s[i];i++){ 53 while(v&&!nxt[v][s[i]-'a'])v=f[v]; 54 v=nxt[v][s[i]-'a']; 55 int tmp=v; 56 while(tmp){ 57 ans+=tail[tmp]; 58 tail[tmp]=0; 59 tmp=last[tmp]; 60 } 61 } 62 return ans; 63 } 64
65 int main(){ 66 int T; 67 scanf("%d",&T); 68 while(T--){ 69 int n; 70 scanf("%d",&n); 71 size=0,newnode(); 72 for(int i=0;i<n;i++){ 73 scanf("%s",word); 74 insert(word); 75 } 76 makenxt(); 77 scanf("%s",s); 78 printf("%d\n",query(s)); 79 } 80 return 0; 81 }