多模匹配-AC自动机

AC自动机 就是 t r i e + K M P \tt trie + KMP trie+KMP

前置知识

t r i e \tt trie trie

失配指针

算法用途

多模式串匹配

算法复杂度

时间

模式串数量 n \tt n n,平均长度 l e n \tt len len

文本串长度 m \tt m m

构建 t r i e \tt trie trie 树: O ( n × l e n ) \tt O(n \times len) O(n×len)

构建失败指针: O ( n × l e n ) \tt O(n \times len) O(n×len)

匹配: O ( 2 × m ) \tt O(2 \times m) O(2×m)

总时间: O ( n × l e n + m ) \tt O(n \times len + m) O(n×len+m)

空间

t r i e \tt trie trie 树一样

算法实现

创建 t r i e \tt trie trie

t r i e \tt trie trie

创建失配指针

K M P \tt KMP KMP 里,当字符串的前后缀相同时,我们就构建失配指针。

所以我们在 t r i e \tt trie trie 树上构建失配指针就是当两个字符串的前后缀相同就行了。

这里采用递推思想,这个节点的失配指针肯定指向这个节点的父亲的失配指针的儿子

f a i l [ i ] = s o n [ f a i l [ f a t h e r [ i ] ] ] [ s t r [ i ] ] \tt fail[i] = son[fail[father[i]]][str[i]] fail[i]=son[fail[father[i]]][str[i]]

初始化:深度为 2 \tt 2 2 的节点的 f a i l \tt fail fail 指针肯定指向根节点(根节点深度 1 \tt 1 1

然后用这个办法 b f s \tt bfs bfs 整颗 t r i e \tt trie trie树就行了

算法优化

我们发现, A C \tt AC AC 自动机每次跳 f a i l \tt fail fail ,最坏情况下只能向上跳一层。

这样,我们在匹配的是后,时间复杂度是 O ( n × m ) \tt O(n\times m) O(n×m) n \tt n n 为文本串长度, m \tt m m 为模式串长度)

有没有办法优化呢?当然有。

我们可以先在匹配的时候用懒标记,记录下要暴力搜索的地方。

再把 f a i l \tt fail fail 指针看成边,跑一遍拓扑来计算答案就行了。

代码

A C \tt AC AC 自动机 + + + 拓扑优化

#include<iostream>
#include<queue>
using namespace std;

vector<int> a[1000010];
int son[1000010][26];
int fail[1000010];
int ans[1000010];
int num[1000010];
int flag[1000010];
int ind[1000010];
queue<int> que;
queue<int> que2;
int cnt, n;

int insert(string str, int id)//插入
{
	int p = 0;
	for(int i = 0; i < str.size(); i++)
	{
		if(son[p][str[i] - 'a'])
		{
			p = son[p][str[i] - 'a'];
		}
		else
		{
			son[p][str[i] - 'a'] = ++cnt;
			p = cnt;
		}
	}
	num[p]++;
	a[p].push_back(id);
	return 0;
}

int get_fail()//创建fail指针
{
	for(int i = 0; i < 26; i++)
	{
		if(son[0][i])
		{
			que.push(son[0][i]);
			ind[0]++;
		}
	}
	while(!que.empty())
	{
		int u = que.front();
		que.pop();
		for(int i = 0; i < 26; i++)
		{
			if(son[u][i])
			{
				fail[son[u][i]] = son[fail[u]][i];
				ind[son[fail[u]][i]]++;
				que.push(son[u][i]);
			}
			else
			{
				son[u][i] = son[fail[u]][i];
			}
		}
	}
	return 0;
}

int find(string str)//多模匹配
{
	int p = 0;
	for(int i = 0; i < str.size(); i++)
	{
		p = son[p][str[i] - 'a'];
		flag[p]++;
	}
}

int topo()//拓扑排序
{
	for(int i = 0; i <= cnt; i++)
	{
		if(!ind[i])
		{
			que2.push(i);
		}
	}
	while(!que2.empty())
	{
		int u = que2.front();
		que2.pop();
		for(int k : a[u])
		{
			ans[k] += flag[u];
		}
		ind[fail[u]]--;
		flag[fail[u]] += flag[u];
		if(!ind[fail[u]])
		{
			que2.push(fail[u]);
		}
	}
}

int main()
{
	scanf("%d", &n);
	string str2 = "";
	for(int i = 0; i < n; i++)
	{
		string str3;
		cin >> str3;
		insert(str3, i);
	}
	cin >> str2;
	get_fail();
	find(str2);
	topo();
	for(int i = 0; i < n; i++)
	{
		printf("%d\n", ans[i]);
	}
}

例题

P3808   【模板】AC自动机(简单版) \color{3498DB}{\texttt{P3808 【模板】AC自动机(简单版)}} P3808 【模板】AC自动机(简单版)

P3796   【模板】AC自动机(加强版) \color{3498DB}{\texttt{P3796 【模板】AC自动机(加强版)}} P3796 【模板】AC自动机(加强版)

P5357   【模板】AC自动机(二次加强版) \color{9D3DCF}{\texttt{P5357 【模板】AC自动机(二次加强版)}} P5357 【模板】AC自动机(二次加强版)

P5231   [JSOI2012]玄武密码 \color{9D3DCF}{\texttt{P5231 [JSOI2012]玄武密码}} P5231 [JSOI2012]玄武密码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值