今天就看了AC自动机
定义和概念什么的都不说了 直接放模板 今天搞明白了原理,然后操作过程在模板的每一个地方我都加上了注释
半天的劳动成果啊,,,,,,至于原理什么的可以去参考下大佬们的博客,原理写的都是蛮清楚的
为了搞清楚AC自动机是个啥 又看视频又看博客又看Word文档,最后加上讨论,还是有几个地方不是很清楚
不过讨论是个好东西,一讨论好多地方就清楚了一点
我这个模板是用指针写的,而书上和饶齐的模板都是用的数组,但是感觉指针的代码更容易读懂一些,如果大家想看的话
可以在理解完原理的基础上来找我这个模板去看,可能更容易理解一些,有可能看不懂的地方我都加上了注释,都是心血
啊,心态炸裂。。。。。。
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
//:给你一个文本和多个单词,问你出现了多少个单词。注意单词可能会重复。
const int MAXN = 1000001; //模式串的最大长度MAXN - 1
const int MAXM = 51; //单词最大长度为MAXM - 1
const int KEYSIZE = 26; //26个小写字母
struct Node {
Node *fail; //失配指针 指向和当前结点的某个点的后缀匹配的最长前缀的位置
Node *next[KEYSIZE]; //儿子结点个数 子节点指针
int count; //单词个数
Node() {
fail = NULL;
count = 0;
memset(next, 0, sizeof(next));
}
~Node() {
delete next;
}
}*q[MAXN/2];
void insert(char *str, Node *root)//模式串插入 构建字典树
{
Node *p = root;
int i = 0;
while(str[i]){
//获取字母对应id
int index = str[i] - 'a';
//检查子节点是否存在,不存在则创建新的子结点
if(p -> next[index] == NULL)
p -> next[index] = new Node();
p = p -> next[index];
i ++;
}
p -> count ++; //在单词的最后一个结点count + 1,代表一个单词
}
void build_ac_automation(Node *root)//构建fail指针 基于bfs实现
{
root -> fail = NULL;
//根结点的失配指针,直接指向NULL
//对于根结点下所有的子节点(与根结点直接相连的结点的fail肯定直接指向root)
//失配指针一定是指向root的(当一个字符都不能匹配的时候,自然不存在更短的能够与之匹配的前缀了)
int head, tail;
head = tail = 0;
q[tail ++] = root;
//逐层计算每一层的结点的fail指针
//当每个结点计算完毕,保证它所有的后继都计算出来
//每次弹出一个结点temp,询问它的每个字符对应的子结点
//第i个子结点记为 temp->next[i]
while(head < tail) {
Node *temp = q[head ++];
for(int i = 0; i < KEYSIZE; i ++) {
//如果temp->next[i]==NULL
//那么temp->next[i]指向temp的失配指针的i号子结点
//temp->next[i]=temp->fail->next[i]
//如果temp->next[i]不等于NULL
//需要构造temp->next[i]的失配指针
//temp[i]->next[i]->fail=temp[i]->fail->next[i]
if(temp -> next[i] != NULL) {
if(temp == root){
temp -> next[i] -> fail = root;
}else {
Node *p = temp -> fail;
while(p != NULL) {
if(p -> next[i] != NULL) {
temp -> next[i] -> fail = p -> next[i];
break;
}
p = p -> fail;
}
if(p == NULL)
temp -> next[i] -> fail = root;
}
q[tail ++] = temp -> next[i];
}
}
}
}
//利用fail指针进行匹配
int query(char *str, Node *root)//目标串匹配
{
int i = 0, cnt = 0;
Node *p = root;
while(str[i]) {
int index = str[i] - 'a';
//根据目标串给定的字符进行遍历
//每次检查当前结点是否为结尾结点
//还需要检查失配指针指向的结点
while(p -> next[index] == NULL && p != root) p = p -> fail;
p = p -> next[index];
//当前单词从来没有出现过的话直接回到匹配之初
p = (p == NULL) ? root : p;
Node *temp = p;
//找到一个子串以temp结点结尾
//可以统计子串数目或者是输出子串的位置
//累加所有的count即为模式串的个数
while(temp != root && temp -> count != -1) {
cnt += temp -> count;
temp -> count = -1;
temp = temp -> fail;
}
i ++;
}
return cnt;
}
int main()
{
int n, cas;
Node *root;
char keyword[MAXM]; //单词
char str[MAXN]; //模式串
scanf("%d", &cas);//case
while(cas --) {
scanf("%d", &n);
getchar();
root = new Node();
while(n --) {
scanf("%s", keyword);
insert(keyword, root);
}
build_ac_automation(root);
scanf("%s", str);
printf("%d\n", query(str, root));
}
return(0);
}