AC 自动机

Kmp 是在一个文本串中,求一个模式串的出现次数。
AC 自动机是求有多少个模式串在一个文本串中出现过。
AC 自动机 = = =Trie + + + Kmp

fail 指针

暴力的写法是从 Trie 走,失配就回到根节点。

和 kmp 的 nxt 数组相似的,如果求出了每个点的 fail 指针,那么在失配后,可以直接跳到 fail 指针指向的点。那么如何求 fail 呢?

  • 令根节点的深度为 1 1 1,那么对于所有深度为 2 2 2 的点,它们的 fail 指针都指向根节点。
  • 从根节点进行 bfs,对于一个点 x x x,如果没有儿子,那么把它的儿子设为 f a i l ( x ) fail(x) fail(x)
  • 否则,将 f a i l ( y ) fail(y) fail(y) 设为 s o n f a i l ( x ) son_{fail(x)} sonfail(x)
void getfail() {
	queue <int> q;
	for (int i = 0; i < 26; i++) 
		if (trie[0][i]) {
			fail[trie[0][i]] = 0;
			q.push(trie[0][i]);
		}
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = 0; i < 26; i++){
			int y = &trie[x][i];
			if (y) {
				fail[y] = trie[fail[x]][i];
				q.push(y);
			} else y = trie[fail[x]][i];
		}		
	}
}

查询

cnt 为该模式串出现的次数。

int query(string s) {
	int x = 0, res = 0;
	for (int i = 0; i < s.size(); i++) {
		x = trie[x][s[i] - 'a'];
		for (int j = x; j && vis[j] != -1; j = fail[j]) {
			res++; 
			vis[j] = -1; //因为求的是多少个不同的模式串,所以不能重复统计
		}	
	}
	return res;
}

加强

如果要求出现次数最多的模式串怎么办呢。

只要在建 Trie 树的时候,标记每个点属于哪个模式串,在查询时也把答案存到对应的模式串中即可。值得一提的是,因为要重复统计,所以在 query 中不要打标记。

二次加强

刚刚的 AC 自动机,fail 是在暴力跳的,最坏复杂度是 O ( t p ) O(t p) O(tp) 的,因为每次跳 fail 只让深度减少一,而且还没有打标记。那么现在要对 AC 自动机进行优化,让每个点只经过一次。

可以发现,对于文本串的每个字符,我们都要去暴力跳一下 fail,那么跳到的 fail 直接计数器加一。那么可以用懒标记的思想,在每个点打个标记,表示它要跳多少次 fail。最后再算总账,这样每个点就跳了一次。

不过要确定跳的顺序,一定先把深度大的标记传给深度小的,再把深度小的传给深度更小的。那么把 fail 指针看成有向边,这样可以拓扑排序来更新了。

void topsort() {
	queue <int> q;
	for (int i = 1; i <= cnt; /*Trie 节点数*/ i++)
		if (!in[i]) q.push(i); // fail 指针入度为 0
	while (!q.empty()) {
		int x = q.front(); q.pop();
		vis[trie[x].flg] = trie[x].tag; // 每个模式串出现的次数
		int y = trie[x].fail; in[y]--;
		trie[y].tag += trie[x].tag; 
		if (!in[y]) q.push(y); 
	} 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值