首页 > 其他分享 >AC 自动机查漏补缺

AC 自动机查漏补缺

时间:2024-08-22 21:38:26浏览次数:13  
标签:AC int 查漏 Trie Fail fail 自动机

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 指针的构建根本就不是路径压缩,而是记忆化。

标签:AC,int,查漏,Trie,Fail,fail,自动机
From: https://www.cnblogs.com/WerChange/p/18374812

相关文章

  • C# start thread include Thread,Task,Async/Await,BackgroundWorker,ThreadPool,Time
    usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Linq;usingSystem.Text;usingSystem.Threading;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Controls;usingSystem.Windows.Data;using......
  • 6. MAC功能描述(I)
    6.MAC功能描述6.1.设备类型和约定6.1.1.设备类型有两种设备类型:全功能设备FFD(full-functiondevice)可以作为个人区域网PAN(personalareanetwork)协调器、协调器、设备、RFD-TX设备、RFD-RX设备或其任何组合来操作。5.8.-精简功能设备RFD(reduced-functiondevice)不得作......
  • 041 Time to Practice Dynamic Styling - Problem
    示例index.html<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width,initial-scale=1.0"/><title>VueBa......
  • Terraform中的for_each和count
    通过Terraform创建云主机时,在某些业务场景下,一个机器需要挂载多个云盘,一般云厂商都是单独创建云主机和云硬盘然后通过attachment的资源去挂载,因此我们的模板大致如下:resource"tencentcloud_instance""basic"{instance_name=var.instance_namepassword="xxx"}......
  • tanstack react-form antd示例
    import{useForm}from"@tanstack/react-form";import{zodValidator}from"@tanstack/zod-form-adapter";import{z}from"zod";importtype{FieldApi}from"@tanstack/react-form";import{Button,Input,Radio......
  • pikachu靶场XXS通关攻略
     1.反射型xxs(get)在搜索框输1界面有停留 进行xxs攻击输入时发现长度不够F12打开控制台修改输入<script>alert(1)</script>2反射型xxs(post)点击提示进行登录输入<script>alert(1)</script>3存储型xxs输1停留界面输入<script>alert(1)</script>4.DOM型xxs......
  • 【Windows Server2016下Oracle11g DG配置实操步骤】
    WindowsServer2016下Oracle11gDG配置实操步骤文章目录WindowsServer2016下Oracle11gDG配置实操步骤前言一、部署规划1.1、虚拟机搭建:1.2、环境规划:1.3、主库操作系统配置1.4、数据库安装和实例创建1.5、监听配置1.6、网络配置1.7、克隆虚拟机二、主库配置2.1、查看......
  • CAAC小型六旋翼训练无人机技术详解
    电动六旋翼无人机,该无人机采用横向折叠臂,性能优秀、操控简单、安全性高,适合用于基础多旋翼飞行技能训练。同时,该无人机符合《民用无人机驾驶员管理规定》中关于多旋翼无人机训练类别的要求,可用于多旋翼无人机实践飞行训练。1.飞行原理与结构CAAC(中国民用航空局)认证的小型......
  • 金蝶云星空元数据冲突SVN:replaced,tree conflict树冲突解决过程
    问题截图: 解决方式:      ......
  • Webpack 核心流程
    我们是袋鼠云数栈UED团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。本文作者:霜序三个阶段初始化阶段初始化参数:从配置文件、配置对象、shell参数中读取,与默认的配置参数结合得出最后的参数。创建编译器对象:通......