AC自动机

AC自动机算是在KMP的基础上面进行拓展。

关于KMP

kmp问题解决的是a关于b字符串的匹配问题,可以快速计算出a的子串中是否含有b
(这个当然可以用hash解决,但是hash里面没有nxt数组!这个当然也可以用二分解决,沉默)

暴力做法:枚举每一个起点,然后重复枚举b的每一个字符,看是否匹配,时间复杂度O(nm)

nxt数组:nxt[i]表示在字符串s中,以s[i]结尾的后缀能够匹配从1开始的最长非平凡前缀的位置。

注:非平凡前缀和非平凡后缀不包括字符串本身。

void get_next()
{
//	nxt[0] = nxt[1] = 0;非平凡的前缀 
	for (int i = 2, j = 0; i <= n; i ++)
	{
		while(j && s[i] != s[j + 1]) j = nxt[j];
		if(s[j + 1] == s[i]) j ++;
		nxt[i] = j;
	}
	return ;
}

通过一系列复杂的证明,在时间复杂度O(n)内,我们可以判断字符串的匹配情况。

具体操作:当发现pos位置不匹配时,我们将匹配字符串的指针j跳到nxt[j],然后看j + 1和pos位置是否匹配,如果不匹配的话,就一直跳,直到为0。j更新完成以后,pos慢慢往前面移动就可以了。

搜索关键词

#include <bits/stdc++.h>
using namespace std;
const int M = 1000005, N = 500005;
int n, m, idx = 0, cnt[N], nxt[N];
struct node
{
	int a[26];
}b;
vector<node> tr;  
char s[M];
void insert()
{
	int len = strlen(s + 1);
	int p = 0;
	for (int i = 1; i <= len; ++ i)
	{
		if(!tr[p].a[s[i] - 'a']) tr[p].a[s[i] - 'a'] = ++idx, tr.push_back(b), p = idx;
		else p = tr[p].a[s[i] - 'a'];
	}
	cnt[p] ++;
	return ;
}
void build_tree()
{
	queue<int> q;
	for (int i = 0; i < 26; ++ i)
	{
		if(tr[0].a[i]) q.push(tr[0].a[i]);
	}
	while(!q.empty())
	{
		int t = q.front();
		q.pop();
		for (int i = 0; i < 26; ++ i)
		{
			if(tr[t].a[i])
			{
				int c = tr[t].a[i];
				int j = nxt[t];
				while(j && !tr[j].a[i]) j = nxt[j];
				if(tr[j].a[i]) j = tr[j].a[i];
				nxt[c] = j; 
				q.push(c);
			}
		}
	}
	return ;
}
int main()
{
	int T;
	scanf("%d", &T);
	for (int i = 0; i < 26; ++ i) b.a[i] = 0;
	while(T --)
	{
		for (int i = 0; i <= tr.size(); ++ i) nxt[i] = 0, cnt[i] = 0;
		tr.clear(); 
		idx = 0;
		tr.push_back(b);
		scanf("%d", &n);
		for (int i =1 ; i <= n; ++ i) 
		{
			scanf("%s", s + 1);
			insert();
		}
		build_tree();
		scanf("%s", s + 1);
		m = strlen(s + 1);
		int p = 0, sum = 0, j = 0;
		for (int i = 1; i <= m; ++ i)
		{
			while(j && !tr[j].a[s[i] - 'a']) j = nxt[j];
			if(tr[j].a[s[i] - 'a']) j = tr[j].a[s[i] - 'a'];
			int p = j;
			while(p) sum += cnt[p], cnt[p] = 0, p = nxt[p];
		}
		printf("%d\n", sum);
	}
	return 0;
}

单词

这里需要用到优化,因为当!tr[t].a[i]的时候需要不断用nxt数组往上面跳,所以我们直接让!tr[t].a[i] = tr[nxt[t]].a[i]就可以直接找到满足条件的后缀位置了。如果tr[t].a[i]是有值的,那么nxt[tr[t].a[i]]应该被更新为tr[nxt[t]].a[i],因为值为零的都被更新了,所以tr[nxt[t]].a[i]就是满足要求的最大的后缀。

最后的询问可以用拓扑序来优化。

#include <bits/stdc++.h>
using namespace std;
const int N = 2000005;
int n, idx = 0, sum[N], col[205];
int nxt[N];
struct node
{
	int a[26];
}b;
vector<node> tr;
char s[N];
int insert()
{
	int len = strlen(s + 1);
	int p = 0;
	for (int i = 1; i <= len; ++ i)
	{
		if(!tr[p].a[s[i] - 'a']) tr.push_back(b), tr[p].a[s[i] - 'a'] = ++ idx, p = idx;
		else p = tr[p].a[s[i] - 'a'];
		sum[p] ++;
	}
	return p;
}
int q[N];
void build_tree()
{
	int tt = 1, hh = 0;
	for (int i = 0; i < 26; ++ i)
	{
		if(tr[0].a[i]) q[++ hh] = tr[0].a[i];
	}
	while(tt <= hh)
	{
		int t = q[tt ++];
		for (int i = 0; i < 26; ++ i)
		{
			int p = tr[t].a[i];
			if(!p) tr[t].a[i] = tr[nxt[t]].a[i];
			else nxt[p] = tr[nxt[t]].a[i], q[++ hh] = p;
		}
	}
	return ;
}

int main()
{
	for (int i = 0; i < 26; ++ i) b.a[i] = 0;
	tr.push_back(b);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; ++ i)
	{
		scanf("%s", s + 1);
		col[i] = insert();
	}
	build_tree();
	for (int i = idx; i >= 1; -- i) sum[nxt[q[i]]] += sum[q[i]];
	for (int i = 1; i <= n; ++ i) printf("%d\n", sum[col[i]]);
	return 0;
}

posted @   Helioca  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
Document
点击右上角即可分享
微信分享提示