今天看AC自动机,看得迷迷糊糊,看起来就是字典树+KMP。字典树还好说,当时KMP有点没看懂,又看了一下,这次KMP倒是懂了,AC自动机还是有点没看懂。
照着博客上的模板试着敲了一遍。构造next数组那里还是有点看不懂。还得继续看。
#include<bits/stdc++.h>
using namespace std;
const int maxnode=11000;
const int sigma_size=26;
struct AC_Auto
{
int ch[maxnode][sigma_size];
int val[maxnode];//字符串的节点字符非0;
int next[maxnode];//小串的前i各元素最长公共前缀和后缀的长度
int last[maxnode];//last[i]==j j节点表示的是i节点单词的后缀,且j节点是单词末字母节点
int sz;
void Clear()//初始化0节点
{
sz=1;
memset(ch[0],0,sizeof(ch[0]));
val[0]=0;
}
int idx(char ch)
{
return ch-'a';
}
void Insert(char *s,int v)//v非0表示一个单词末尾
{
int u=0,n=strlen(s);
for(int i=0;i<n;i++)
{
int id=idx(s[i]);
if(ch[u][id]==0)
{
ch[u][id]=sz;
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
sz++;
}
u=ch[i][id];
}
val[u]=v;
}
void getNext()//构造next数组和last数组
{
queue<int>q;
last[0]=next[0]=0;
for(int i=0;i<sigma_size;i++)
{
int u=ch[0][i];
if(u)//存在以i开头的单词,u为节点编号
{
next[u]=last[u]=0;
q.push(u);
}
}
while(!q.empty())//按bfs顺序计算next数组的值
{
int now=q.front();
q.pop();
for(int i=0;i<sigma_size;i++)
{
int u=ch[r][i];//检索下一层
if(u==0) continue;
q.push(u);
int v=next[now];
while(v&&ch[v][i]==0)
v=next[v];
next[u]=ch[v][i];
last[u]=val[next[u]]!=0?next[u]:last[next[u]];
}
}
}
void print(int i)//打印与节点i后缀相同的前缀节点编号,先保证val[i]>0
{
if(i)
{
printf("%d\n",i);
print(last[i]);
}
}
void Find(char *s)
{
int j=0,n=strlen(s);
for(int i=0;i<n;i++)
{
int id=idx(s[i]);
while(j&&ch[j][id]==0)
j=next[j];
j=ch[j][id];
if(val[j])
print(j);
else if(last[j]!=0)
print(last[j]);
}
}
};
问了一下组长,他看的是那种动态指针的
#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);
}