AC 自动机查漏补缺

推荐在 cnblogs 上阅读

AC 自动机查漏补缺

前言

今年 1 月份学过一次,当时自以为掌握得很好,实际上就是依托答辩。而且还有很多地方是有严重误导性的。所以这篇查漏补缺就是记录一下自己对 AC 自动机尚不完全掌握的地方。并对之前的那篇不太正确的题解进行纠正。

因此,在这样的背景下,这篇文章注定就不是给初学者看的,是大致了解了 AC 自动机是什么后的一篇提高读物。

当然你硬要初学就来看也没问题,毕竟此篇文章与重头开始写没有什么区别。

三问 AC 自动机

第一问:AC 自动机到底是什么?

KMP 是解决单模式串匹配问题的算法,而 AC 自动机则可以解决多模式串匹配问题。

第二问:AC 自动机靠什么实现?

通过多模式串建起一棵 Trie 树,再构建 Trie 上每个点的失配指针(fail),由此可得 Fail 树。将文本串在 Trie 上留痕。然后就可以得到文本串前缀的信息。根据 Fail 树的定义可以更新这些前缀的后缀,即文本串的子串。同时如果该子串如果是模式串则可以维护模式串的信息了。

第三问:AC 自动机为什么叫“自动机”?

自动机是一种数学模型,至于它的深层的定义可以去看《自动机 - OI Wiki》。

三问 Fail 树

第一问:Fail 树是怎么冒出来的?

AC 自动机一个重要的步骤是构建失配指针,即 fail 指针。某个点的 fail 指针所指向的点在 Trie 上对应的字符串是该点在 Trie 上对应的字符串的最大后缀(“对应的字符串”指 Trie 上的根节点到此点的路径上的字符构成的串)。所以每个点都只有一个 fail 指针(可能指向根节点),每个点也有可能被多个节点的失配指针指向。这个就很像父子关系,所以 fail 指针构造出来后,即得到了一棵 Fail 树。

第二问:Fail 树和 Trie 树有什么区别?

Fail 树上每个节点的祖先都是该点在 Trie 上对应字符串的后缀。Trie 树是把每个模式串塞进去而建起来的。当文本串在 Trie 上留痕后,一个留痕过的点从根节点到它的路径上形成的字符串就是文本串的前缀(这个很显然啦~)。

第三问:Fail 树和 Trie 树有什么联系?

首先是在 Trie 上将文本串留痕,边留痕边在该节点记录信息。这样结束后便得到了文本串前缀的信息。我们还想处理文本串子串的信息。这时候需要搬出我们的 Fail 树。拓扑排序,从叶子节点将信息传递到父亲节点。为什么呢?因为 Fail 树上的祖先是该点的后缀,而前缀的后缀就是子串。为什么可以这样?因为该点是文本串留痕过的点,说明此时是可以匹配上文本串的。既然该点的串可以匹配上,那该点的串的后缀必然也是可以匹配上的。

典型例题

P3808 AC 自动机(简单版)

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N=1e6+5,M=30;

int n;
char str[N],s[N];
int tr[N][M],fail[N],tot,deg[N],pos[N];
bool vis[N];

void ins(char *s,int j)
{
	int len=strlen(s),root=0;
	for(int i=0;i<len;i++)
	{
		if(!tr[root][s[i]-'a'])
			tr[root][s[i]-'a']=++tot;
		root=tr[root][s[i]-'a'];
	}
	pos[j]=root;
}

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

void AC()
{
	int len=strlen(str),root=0;
	for(int i=0;i<len;i++)
	{
		root=tr[root][str[i]-'a'];
		vis[root]=1;
	}
	for(int i=1;i<=tot;i++)
		deg[fail[i]]++;
	queue<int> q;
	for(int i=1;i<=tot;i++)
		if(!deg[i])
			q.push(i);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(!u) continue;
		vis[fail[u]]=1;
		deg[fail[u]]--;
		if(!deg[fail[u]])
			q.push(fail[u]);
	}
}

signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		ins(s,i);
	}
	scanf("%s",str);
	FL();
	AC();
	int ans=0;
	for(int i=1;i<=n;i++)
		if(vis[pos[i]])
			++ans;
	printf("%lld\n",ans);
	return 0;
}

P3796 AC 自动机(简单版 II)

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N=1e6+5,M=30,L=80;

int n;
char str[N],s[N][L];
int tr[20000][M],fail[N],tot,pos[N];
int vis[N];

void ins(char *s,int j)
{
	int len=strlen(s),root=0;
	for(int i=0;i<len;i++)
	{
		if(!tr[root][s[i]-'a'])
			tr[root][s[i]-'a']=++tot;
		root=tr[root][s[i]-'a'];
	}
	pos[tot]=j;
}

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

void AC()
{
	int len=strlen(str),root=0;
	for(int i=1;i<=tot;i++)
		vis[i]=0;
	for(int i=0;i<len;i++)
	{
		root=tr[root][str[i]-'a'];
		for(int j=root;j;j=fail[j])
			vis[pos[j]]++;
	}
}

signed main()
{
	while(~scanf("%lld",&n)&&n)
	{
		memset(fail,0,sizeof(fail));
		memset(tr,0,sizeof(tr));
		memset(pos,0,sizeof(pos));
		tot=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%s",s[i]);
			ins(s[i],i);
		}
//		cerr<<tot<<"\n";
		scanf("%s",str);
		FL();
		AC();
		int ans=0;
		for(int i=1;i<=n;i++)
			ans=max(ans,vis[i]);
		printf("%lld\n",ans);
		for(int i=1;i<=n;i++)
			if(vis[i]==ans)
				puts(s[i]);
	}
	
	return 0;
}

P5357 【模板】AC 自动机

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N=2e5+5,M=30,L=80;

int n;
char str[N*10],s[N][L];
int tr[N][M],fail[N],tot,deg[N],pos[N];
int vis[N];

void ins(char *s,int j)
{
	int len=strlen(s),root=0;
	for(int i=0;i<len;i++)
	{
		if(!tr[root][s[i]-'a'])
			tr[root][s[i]-'a']=++tot;
		root=tr[root][s[i]-'a'];
	}
	pos[j]=root;
}

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

void AC()
{
	int len=strlen(str),root=0;
	for(int i=0;i<len;i++)
	{
		root=tr[root][str[i]-'a'];
		vis[root]++;
	}
	queue<int> q;
	for(int i=1;i<=tot;i++)
		deg[fail[i]]++;
	for(int i=1;i<=tot;i++)
		if(!deg[i])
			q.push(i);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(!u) continue;
		vis[fail[u]]+=vis[u];
		deg[fail[u]]--;
		if(!deg[fail[u]])
			q.push(fail[u]);
	}
}

signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s[i]);
		ins(s[i],i);
	}
	scanf("%s",str);
	FL();
	AC();
	int ans=0;
	for(int i=1;i<=n;i++)
		printf("%lld\n",vis[pos[i]]);
	return 0;
}

后记

感觉例题没什么好说的,这篇文章本来就不是详解,只是一点查漏补缺。代码都是在写文章当天重写过一遍的,以此来加深印象。

另:之前那篇《浅谈 AC 自动机》有些有错误的地方,例如 fail 指针的构建根本就不是路径压缩,而是记忆化。

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python AC自动机是一个用于字符串匹配的算法,它可以高效地在一段文本中查找多个预定义的模式。它的实现可以使用多种库,其中包括ac自动机python和ahocorasick-python。 ac自动机python是一个对标准的ac自动机算法进行了完善和优化的实现,适用于主流的Python发行版,包括Python2和Python3。它提供了更准确的结果,并且可以通过pip进行安装,具体的安装方法可以参考官方文档或者使用pip install命令进行安装。 ahocorasick-python是另一个实现AC自动机的库,它也可以用于Python2和Python3。你可以通过官方网站或者GitHub源码获取更多关于该库的信息和安装指南。 对于AC自动机的使用,一个常见的例子是在一段包含m个字符的文章中查找n个单词出现的次数。要了解AC自动机,需要有关于模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机算法包括三个步骤:构造一棵Trie树,构造失败指针和模式匹配过程。在构造好AC自动机后,可以使用它来快速地在文本中查找预定义的模式,并统计它们的出现次数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ahocorasick-python:AC自动机python的实现,并进行了优化。 主要修复了 查询不准确的问题](https://download.csdn.net/download/weixin_42122986/18825869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python实现多模匹配——AC自动机](https://blog.csdn.net/zichen_ziqi/article/details/104246446)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值