AC自动机板子

本文介绍AC自动机的实现原理及应用案例,通过构建 Trie 树并设置失败指针,实现高效字符串匹配。适用于多模式匹配场景,如查找一组关键词在文本中出现的次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给出多个单词

再给出一个字符串

问有多少个单词在字符串里出现过(可能有重复的单词)

思路:trie建树   建fail指针   查询

洛谷:P3808 【模板】AC自动机

代码:

#include<bits/stdc++.h>
#define N 500010
using namespace std;
queue<int>q;	
struct Aho_Corasick_Automaton {
	int c[N][26],val[N],fail[N],cnt;//C是trie树   value是单词个数  fail是指针  cnt是trie中第几个点 
	void ins(char *s) { //trie建树
		int len=strlen(s);
		int now=0;
		for(int i=0; i<len; i++) {
			int v=s[i]-'a';
			if(!c[now][v])c[now][v]=++cnt;
			now=c[now][v];
		}
		val[now]++;//以这个点结束的单词数量++ 
	}
	void build() {
		for(int i=0; i<26; i++) 
			if(c[0][i])
				fail[c[0][i]]=0,q.push(c[0][i]);//将所有的根入队列 
		while(!q.empty()) {//建立AC 自动机的核心代码 
			int u=q.front();//当前的点的序号 
			q.pop();
			for(int i=0; i<26; i++)//对这个点的这一层进行遍历 
				if(c[u][i])//如果这个点存在这个字符,这个点的fail指针指向父节点的这个字母(虽然父节点的这个字母可能不存在) 
					fail[c[u][i]]=c[fail[u]][i],q.push(c[u][i]); //将这个点入队 
				else c[u][i]=c[fail[u]][i];//不存在这个字符,这个字符指向父亲节点的这个字符 
		}
	}
	int query(char *s) {
		int len=strlen(s);
		int now=0,ans=0;
		for(int i=0; i<len; i++) {//对母串进行遍历 
			now=c[now][s[i]-'a'];//KMP思想   
			for(int t=now; t&&~val[t]; t=fail[t])//如果这个节点不是根节点  没有被访问过  加上  然后返回它的父节点的其他值加上 
				ans+=val[t],val[t]=-1; 
		}
		return ans;
	}
} AC;
int n;
char p[1000005];
int main() {
	scanf("%d",&n);
	for(int i=1; i<=n; i++)scanf("%s",p),AC.ins(p); //输入一个字符串并且建树
	AC.build();//建立自动机
	scanf("%s",p);
	int ans=AC.query(p);
	printf("%d\n",ans);
	return 0;
}

我的代码(使用了string):

#include<bits/stdc++.h>
#include<queue>
using namespace std;
#define N 501001
queue<int>q;
struct Auto_Ac{
	int trie[N][26],val[N],fail[N],cnt;
	void ins(string s){
		int len=s.size(),now=0;
		for(int i=0;i<len;i++){
			int v=s[i]-'a';
			if(!trie[now][v])trie[now][v]=++cnt;
			now=trie[now][v];
		}
		val[now]++;
	} 
	
	void build(){//建立fail指针
		for(int i=0;i<26;i++)if(trie[0][i])fail[trie[0][i]]=0,q.push(trie[0][i]);
		int u;
		while(!q.empty()){
			u=q.front();
			q.pop();
			for(int i=0;i<26;i++){
				if(trie[u][i])fail[trie[u][i]]=trie[fail[u]][i],q.push(trie[u][i]);
				else trie[u][i]=trie[fail[u]][i];//如果这个点不存在值,那么这个点的后续点是父亲的这个点的指向父亲的这个
												//点的后续点,指向叔叔   或者堂爷 
			}
		}
	}
	
	int ask(string s){
		int now=0,ans=0,len=s.size();
		for(int i=0;i<len;i++){
			now=trie[now][s[i]-'a'];
			for(int t=now;t&&~val[t];t=fail[t])
				ans+=val[t],val[t]=-1;
		}
		return ans;
	} 
}AC;
int main(){
	int n;
	string s;
	scanf("%d",&n);
	while(n--){
		cin>>s;
		AC.ins(s);
	}
	AC.build(); 
	cin>>s;
	cout<<AC.ask(s);
	return 0;
}
 

hdu2222

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int trie[N][26],cnt=0,fail[N],val[N];
void build(char s[]){
	int now=0,len=strlen(s),x;
	for(int i=0;i<len;i++){
		x=s[i]-'a';
		if(!trie[now][x])trie[now][x]=++cnt;
		now=trie[now][x];
	}
	val[now]++;
}
	
void getfail(){
	queue<int>q;
	for(int i=0;i<26;i++){
		if(trie[0][i]){
			q.push(trie[0][i]);
			fail[trie[0][i]]=0;
		}
	}
	int now=0;
	while(!q.empty()){
		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 s[]){
	int now=0,ans=0,len=strlen(s);
	for(int i=0;i<len;i++){
		now=trie[now][s[i]-'a'];
		for(int j=now;j&&~val[j];j=fail[j]){
			ans+=val[j];
			val[j]=-1;
		}
	}
	return ans;
}
int main(){
	int n,m;
	scanf("%d",&n);
	char s1[100],s[1000006];
	while(n--){
		cnt=0;
		memset(trie,0,sizeof(trie));
		memset(val,0,sizeof(val));
		memset(fail,0,sizeof(fail));
		scanf("%d",&m);
		for(int i=1;i<=m;i++)		
			scanf("%s",s1),build(s1);
		getfail();
		scanf("%s",s);
		printf("%d\n",query(s));
	}
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值