AC自动机

前言

AC自动机是一种多模匹配算法。给出一个目标T和多个模式P1、P2、P3、…、pn,问你有多少个模式在T中出现过,并给出在T中匹配的位置。
复杂度O(n),n为T的长度

步骤

构造Trie树

构造一棵Trie树,作为AC自动机算法的数据结构。将多个模式Pi插入Trie树中。不仅有此前Trie树的性质,节点还增加一个fail指针。如果当前点匹配失败,则将指向当前匹配的字符的指针转移到fail指针指向的地方,使得当前匹配的模式串的后缀和fail指针指向的模式串的前缀相同。

void insert_(char *str)
{
	int len=strlen(str),root=0;
	for(int i=0;i<len;i++){
		int next=str[i]-'a';
		if(!trie[root][next]) trie[root][next]=++tot;
		root=trie[root][next];	
	}
	cntword[root]++;
}

通过BFS构造fail指针

root进入队列,出队。根绝后缀的定义,root的孩子节点的fail都指向root,root的孩子节点入队。其他节点now若有第i个子孩子,将第i个子孩子的fail指针指向now节点的fail指向节点的第i个孩子.因为我们不能漏掉任何一种情况,所以从后缀最大的开始;否则我们就将这个子节点指向now节点fail的第i个子节点

void getFail()
{
	queue<int> q;
	for(int i=0;i<26;i++){
		if(trie[0][i]){
			fail[trie[0][i]]=0;
			q.push(trie[0][i]);			
		}
	}
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int i=0;i<26;i++){
			if(trie[now][i]){
				fail[trie[now][i]]=trie[fail[now]][i];
				q.push(trie[now][i]);
			}
			else
				trie[now][i]=trie[fail[now]][i];
		}
	}
}

扫描目标进行匹配

int query(char *str){
	int now=0,ans=0,len=strlen(str);
	for(int i=0;i<len;i++){
		now=trie[now][str[i]-'a'];
		for(int j=now;j&&cntword[j]!=-1;j=fail[j])
		{
			ans+=cntword[j];
			cntword[j]=-1;
		}
	}
	return ans;
}

模板

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=1e6+10;
int trie[maxn][26];
int flagg[maxn];
int fail[maxn];
int tot;
char s[maxn];
int newNode(){
	for(int i=0;i<26;i++){
		trie[tot][i]=0;
	}
	flagg[tot]=0;
	return tot++;
}
void insert_(char *str)
{
	int len=strlen(str),root=0;
	for(int i=0;i<len;i++){
		int next=str[i]-'a';
		if(!trie[root][next]) trie[root][next]=newNode();
		root=trie[root][next];	
	}
	flagg[root]++;
}
void getFail()
{
	queue<int> q;
	for(int i=0;i<26;i++){
		if(trie[0][i]){
			fail[trie[0][i]]=0;
			q.push(trie[0][i]);			
		}
	}
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int i=0;i<26;i++){
			if(trie[now][i]){
				fail[trie[now][i]]=trie[fail[now]][i];
				q.push(trie[now][i]);
			}
			else
				trie[now][i]=trie[fail[now]][i];
		}
	}
}
int query(char *str){
	int now=0,ans=0,len=strlen(str);
	for(int i=0;i<len;i++){
		now=trie[now][str[i]-'a'];
		for(int j=now;j&&flagg[j]!=-1;j=fail[j])
		{
			ans+=flagg[j];
			flagg[j]=-1;
		}
	}
	return ans;
}
int main()
{
	
}

模板题

Keywords Search HDU - 2222

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=1e6+10;
int trie[maxn][26];
int flagg[maxn];
int fail[maxn];
int tot;
char s[maxn];
int newNode(){
	for(int i=0;i<26;i++){
		trie[tot][i]=0;
	}
	flagg[tot]=0;
	return tot++;
}
void insert_(char *str)
{
	int len=strlen(str),root=0;
	for(int i=0;i<len;i++){
		int next=str[i]-'a';
		if(!trie[root][next]) trie[root][next]=newNode();
		root=trie[root][next];	
	}
	flagg[root]++;
}
void getFail()
{
	queue<int> q;
	for(int i=0;i<26;i++){
		if(trie[0][i]){
			fail[trie[0][i]]=0;
			q.push(trie[0][i]);			
		}
	}
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int i=0;i<26;i++){
			if(trie[now][i]){
				fail[trie[now][i]]=trie[fail[now]][i];
				q.push(trie[now][i]);
			}
			else
				trie[now][i]=trie[fail[now]][i];
		}
	}
}
int query(char *str){
	int now=0,ans=0,len=strlen(str);
	for(int i=0;i<len;i++){
		now=trie[now][str[i]-'a'];
		for(int j=now;j&&flagg[j]!=-1;j=fail[j])
		{
			ans+=flagg[j];
			flagg[j]=-1;
		}
	}
	return ans;
}
int main()
{
	int t,n;
	scanf("%d",&t);
	while(t--){
		tot=0;
		newNode();
		scanf("%d",&n);
		while(n--){
			scanf("%s",s);
			insert_(s);
		}
		getFail();
		scanf("%s",s);
		printf("%d\n",query(s));
	}
	return 0;
}

感想

因为这位博主Trie树的建立方式跟我的一样,所以代码选择了他的。也看了B站很多视频,但还是感觉有很多地方需要多体会。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值