AC自动机的跳转移边优化的理解+比赛模板

先上比赛模板

#include<iostream>
#include<vector>
#include<algorithm>
#include<string.h>
#include<string>
#include<cstdio>
#include<queue>
using namespace std;

#define MAX 500010
#define ll int

//t:trie树   val[i]:标记i是否是一个字符串的结尾  fail[i]:i的失配指针 cnt:节点计数
ll t[MAX][26], val[MAX], fail[MAX], cnt;

// 传入字符串s,初始化字典树
void init(string s) {
	ll len = s.size();	ll now = 0;
	for (int i = 0; i < len; i++) {
		int v = s[i] - 'a';
		if (!t[now][v]) t[now][v] = ++cnt;
		now = t[now][v];
	}
	val[now]++;
}

// 寻找所有失配指针
void build() {
	queue<ll> q;
	// 第一层节点的失配指针指向根0,全部入队t[0][i]是对应的节点编号
	for (int i = 0; i < 26; i++)if (t[0][i])fail[t[0][i]] = 0, q.push(t[0][i]);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		for (int i = 0; i < 26; i++) {
			//u的子节点i的失配指针,等于u的失配指针下同为i的节点编号,如果不存在就是根
			if (t[u][i])fail[t[u][i]] = t[fail[u]][i], q.push(t[u][i]);
			//不存在这个子节点 那么当前节点的这个子节点指向当前节点fail指针的这个子节点
			else t[u][i] = t[fail[u]][i];
		}
	}
}
int query(string s) {
	int len = s.size(); int now = 0, ans = 0;
	for (int i = 0; i < len; i++) {
		now = t[now][s[i] - 'a'];//下一层
		for (int t = now; t && val[t] != -1; t = fail[t])//当此节点存在同时其未被遍历; 
			ans += val[t], val[t] = -1;//当这个值为负时代表已经统计过了
	}
	return ans;
}

int main() {
	int n; cin >> n; string s;
	for (int i = 0; i < n; i++) {
		 cin >> s;
		 init(s);
	}
	build();
	cin >> s;
	cout << query(s) << endl;
}

关于AC自动机的教程已经很多了,最难理解的部分无非是失配指针的构造和跳转移边的原理。所谓失配指针无非是KMP在字典树上的形式,多画画图也还好。让我纠结的反而是跳转移边的必要性和原理。
在这里插入图片描述
对上图,我们在构建fail数组时,有一条语句else t[u][i] = t[fail[u]][i];,这也就是所谓的跳转移边优化。当我们build到最左边的节点d的时候,他的fail节点是他的父节点的fail节点的d节点的编号,而他的下属所有不存在的节点都会执行跳转移边,具体而言就是左下角的e节点本身是不存在的,此时我们执行t[u][i] = t[fail[u]][i]也就是右边他的fail节点的e节点的编号给他。当我们查询的时候,我们需要匹配的串是abcde,那么匹配到左下角的d节点我们会统计一次(因为d是abcd的结尾字符),然后now=t[now]['e'-'a'],(now是d的编号),now经过修改指向了右侧的e节点(也就是说我们在这里建立了一个虚拟的e节点,指向其可以得到匹配的地方),然后我们直接将e节点统计一次(e是bcde的终止节点),就得到了答案。如果没有这一步跳转移边,我们就需要每次都跳fail,如果上图的e节点在根节点上,那么跳转移边的结果是所有虚拟的e节点都指向0,只需要一步跳转。而跳fail节点的话,需要4步(每个d向fail节点跳,跳完还不是不能匹配e,继续跳,直到根节点)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值