AC自动机(查询)

上面讲了AC自动机是如何建树和建自动机的,这里要讲的是AC自动机的查询和各个数组的功能和作用。

其实AC自动机的查询和KMP算法是及其相近的,都是一个指针跑主串,另一个指针跑ne串(这里就是回跳边)。

话都说到这了,不妨先来看看ne的真实用处吧。上次有提过,ne数组存的其实就是最长的后缀模式串,当我们找到一个字串在主串中匹配的时候,我们并不能直接继续下去,因为如果两个单词之间存在借位的情况,我们就会忽略从而导致错误,所以我们需要记录下当前字母先前能回跳的位置,这样我们才能将我们所要的字串一网打尽,和KMP其实是完全相同的。

那么转移边呢,我们说过我们要用一个指针去遍历树,但是我们遍历树的时候一但到头了怎么办,是不是要沿着原路反回。现在我们用一个转移边就可以节省这一部分的操作,那么为什么要把转移边建在自己回跳边的儿子上呢?其实是为了契合上面所建的回跳边,这样我们的跑主串的指针就一定会是不回退的,只需要回跳边不断操作,主串完全就是一个匹配版,不需要进行复杂的操作。

代码如下:

int query(char *s)
{
	int ans = 0;
	
	for(int k = 0, i = 0; s[k]; k ++)
	{
		i = ch[i][s[k] - 'a'];
		
		for(int j = i; j && ~cnt[j]; j = ne[j])
		{
			ans += cnt[j];
			cnt[j] = -1;
		}
	}
	
	return ans;
}

这里需要注意,我们的计数的维护,当我们找到某一个字串后,我们会加上它在Trie树中存储的个数,同时别忘了清零,否则会多加,这里用的是位运算的小技巧。

贴一道例题:

https://www.luogu.com.cn/problem/P3808https://www.luogu.com.cn/problem/P3808

题目描述

给定 𝑛 个模式串 𝑠𝑖 和一个文本串 𝑡,求有多少个不同的模式串在文本串里出现过。

两个模式串不同当且仅当他们编号不同。

输入格式

第一行是一个整数,表示模式串的个数 𝑛。

第 2 到第 (𝑛+1) 行,每行一个字符串,第 (𝑖+1) 行的字符串表示编号为 𝑖的模式串 𝑠𝑖。

最后一行是一个字符串,表示文本串 𝑡。

输出格式

输出一行一个整数表示答案。

输入输出样例

输入 #1复制

复制代码
3
a
aa
aa
aaa

输出 #1复制

复制代码
3

输入 #2复制

复制代码
4
a
ab
ac
abc
abcd

输出 #2复制

复制代码
3

输入 #3复制

复制代码
2
a
aa
aa

输出 #3复制

复制代码
2

代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

int ch[N][26], cnt[N], ne[N];
int n, idx;
char s[N];

void insert(char *s)
{
	int p = 0;
	
	for(int i = 0; s[i]; i ++)
	{
		int j = s[i] - 'a';
		if(!ch[p][j])
			ch[p][j] = ++ idx;
		p = ch[p][j];
	}
	cnt[p] ++;
}

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

int query(char *s)
{
	int ans = 0;
	
	for(int k = 0, i = 0; s[k]; k ++)
	{
		i = ch[i][s[k] - 'a'];
		for(int j = i; j && ~cnt[j]; j = ne[j])
		{
			ans += cnt[j];
			cnt[j] = -1;
		}
	}
	
	return ans;
}

int main()
{
	cin >> n;
	
	while(n --)
	{
		cin >> s;
		
		insert(s);
	}
	
	build();
	
	cin >> s;
	
	int ans = query(s);
	
	cout << ans << endl;
}

我发现了,其实这些看起来比较难的算法并没有用什么特别高级的东西,大多都是需要动脑想的,只要将各个数组的作用,以及特殊的定义想明白其实还是简单的。

相关推荐
代码雕刻家6 分钟前
课设实验-数据结构-单链表-文教文化用品品牌
c语言·开发语言·数据结构
sp_fyf_202422 分钟前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
小字节,大梦想1 小时前
【C++】二叉搜索树
数据结构·c++
我是哈哈hh2 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
Tisfy2 小时前
LeetCode 2187.完成旅途的最少时间:二分查找
算法·leetcode·二分查找·题解·二分
Mephisto.java2 小时前
【力扣 | SQL题 | 每日四题】力扣2082, 2084, 2072, 2112, 180
sql·算法·leetcode
robin_suli2 小时前
滑动窗口->dd爱框框
算法
丶Darling.2 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树
labuladuo5203 小时前
Codeforces Round 977 (Div. 2) C2 Adjust The Presentation (Hard Version)(思维,set)
数据结构·c++·算法
Indigo_code3 小时前
【数据结构】【链表代码】合并有序链表
数据结构·windows·链表