AC 自动机学习笔记

前言

AC 自动机用于处理多模式串匹配长字符串问题。

前置知识:Trie KMP

实现

About fail

先给所有模式串建一个 Trie,然后使用一个 fail 指针。

fail[u] 指向结点 uuu 所代表的字符串的最长在 Trie 上存在的后缀。

假如 uuu 的父亲是 ppp,并且 (u,p)(u, p)(u,p) 表示字符 ccc,那么如果 fail[p]ccc 这个儿子,那么 fail[u] = t[fail[p]][c],否则看 fail[fail[p]] 有没有 ccc 这个儿子,直到 p=0p = 0p=0,不存在后缀,那么 fail[u]=fail[0]=0fail[u] = fail[0] = 0fail[u]=fail[0]=0

注意,由于 fail 指针是在比 uuu 深度小的字符串上乱跳的,所以要 bfs 处理。

void build(){
	queue<int> q;
	for(int i = 0;i < 26;i++)
		if(tr[0][i])
			q.push(tr[0][i]);
	while(!q.empty()){
		int i = q.top();	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];
		}
	}
}

代码里面有一个很妙的点,就是这句:

else
	tr[u][i] = tr[fail[u]][i];

我们发现,之前如果在跳 fail 指针的时候继续跳的条件就是不存在 iii 儿子。那么我在不存在的儿子上面直接先跳,相当于一个路径压缩,可达大大提高效率。

About query

关于 AC 自动机是如何匹配的。

核心思想是每次找儿子,如果没有儿子的话就跳 fail 指针,直到有儿子。


P3808

先看例题:假如我们想知道现在有几个模式串在长字符串里。

int query(string str){
	int len = str.size();
	int u = 0, ans = 0;
	for(auto c : str){
		u = tr[u][c - 'a'];// 指向结点的 c 儿子
		for(int v = u;v && cnt[v] != -1;v = fail[v]){// 一直找 fail 指针,直到找到之前找过的路。
		// 容易发现只有当找到(表示模式串的结点)的时候才有贡献,所以其他时候 ans += cnt[v] 这句话是没用的
			ans += cnt[v];
			cnt[v] = -1;
		} 
	}
}

我们知道假儿子实际上指向的是 fail 指针,所以 u = tr[u][c - 'a'] 相当于跳了 fail 指针。

然后我们要找这个字符串表示的后缀中有多少个模式串,继续跳 fail,直到找过了。


P3796

再看例题:我们要求在长字符串中出现次数最多的若干个模式串。

void query(string str){
	int u = 0;
	for(auto c : str){
		u = t[u][c - 'a'];// 指向结点的 c 儿子
		for(int v = u; v; v = fail[v]){// 一直找 fail 指针。
		// 容易发现只有当找到(表示模式串的结点)的时候才有贡献,所以其他时候这句话是没用的
			ans[f[v]].first++;
		} 
	}
}

其中,f[i] 表示在字典树中以 iii 结尾的结点所表示的模式串编号。若 iii 不表示模式串结尾,那么 fi=0f_{i}= 0fi=0,这个时候,ans[f[v]] 无论怎么加,对答案都没有贡献。

关于 AC 自动机的套路

Fail 树

在 AC 自动机上面,满足一个奇妙的性质:fail 指针构成了一棵树。

假如我们需要统计每个模式串在长字符串中出现的次数,用上面的做法会 TLE。

我们知道,上面的做法实际上是将这个点到根的 fail 链上的所有结点都 +1+1+1,如果结点刚好表示一个模式串,那么对答案的共享就 +1+1+1

实际上,我们只要在结点上打标机,表示从结点到跟的所有经过的点都 +1+1+1

最后,再进行一次树形 dp 即可。

su=∑v∈son(u)sv s_{u} = \sum\limits_{v \in son(u)} s_v su=vson(u)sv

例题 P5357,统计每个模式串在长字符串中出现的次数。

思路如上,没有细节。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值