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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人