只是给忘记模板时的我看的
AC自动机
大概流程
首先对所有模式串建出 T r i e Trie Trie 树,并标记。
f a i l fail fail 的定义:设 i i i 节点所代表的字符串为 S S S,则 f a i l i fail_i faili 表示 S S S 的所有后缀里面,在 T r i e Trie Trie 树中出现过的最长的那个串所代表的节点。
然后通过 bfs exttt{bfs} bfs 求出 f a i l fail fail,代码如下:
void getfail()
{
queue<int>q;
for(int i=0;i<26;i++)
if(t[0].ch[i])
q.push(t[0].ch[i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(t[u].ch[i])
{
t[t[u].ch[i]].fail=t[t[u].fail].ch[i];
q.push(t[u].ch[i]);
}
else t[u].ch[i]=t[t[u].fail].ch[i];//tag1
}
}
}
其中为什么 t a g 1 tag_1 tag1 处可以将 u u u 的儿子直接指向 f a i l u fail_u failu 的儿子 v v v:
首先实际的操作应该是新建一个虚拟节点 n e w new new,使 n e w new new 为 u u u 的儿子,且 f a i l n e w = v fail_{new}=v failnew=v。
又由于 n e w new new 本身是新建的节点,没有任何儿子,所以 n e w new new 的儿子全都是要靠新建虚拟节点构成。
所以 n e w new new 的子树其实和 v v v 的子树是一模一样的。
那我们不妨用同一棵子树表示他们,也就是说让 u u u 的儿子指向 v v v 而不是新建节点。
然后由于 n e w new new 树的 f a i l fail fail 全部都是指向 v v v 树的,所以合并到一起不会对 f a i l fail fail 产生影响。
那么 getfail ( ) operatorname{getfail}() getfail() 之后原来的 T r i e Trie Trie 树就会变成一个 DAG 了。
实际应用
一、模式串与文本串匹配上的应用
原理
首先通过递归 f a i l fail fail,就可以遍历某个串的所有在模式串中出现过的后缀。
同样,如果建立 f a i l fail fail 树( f a i l i → i fail_i o i faili→i),就可以通过遍历某一个点 u u u 的子树(设 u u u 所代表的串为 s s s),遍历所有以 s s s 为后缀的串。(也就是往 s s s 的前面加字符)
其次,对于原 T r i e Trie Trie 树中的某一个节点 u u u(设其代表的串为 s s s),可以遍历统计 u u u 子树内的所有点,遍历所有以 s s s 为前缀的串。(也就是往 u u u 后面加字符)
那么综合上面两个操作,对于某个串 t t t,我们可以求出所有满足 t t t 是 s s s 的子串的 s s s 串的信息。
时间复杂度为 O ( n ) O(n) O(n)(遍历一遍 T r i e Trie Trie 树+一遍 f a i l fail fail 树)。
所以对于解决模式串类的问题,AC 自动机的本质就是对于每一种字符串,除了记录在它后面加字符能到达的出现过的串( T r i e Trie Trie 树),还记录了在它前面加字符能到达的出现过的串( f a i l fail fail 树)。
那么对于 s s s 串的子串信息,我们可以对 s s s 的前缀跳 f a i l fail fail 链。而对于 t t t 串的扩展串信息( t t t 是某个串的子串),我们可以在 f a i l fail fail 树中遍历 t t t 树的子树,再在 T r i e Trie Trie 树中遍历 遍历到的点 的子树。
例题
1.请你分别求出每个模式串 T i T_i Ti 在文本串 S S S 中出现的次数。
可以直接按我们刚刚的做法来做(跳 S S S 前缀的 f a i l fail fail 链),但是会 T 飞。
考虑优化,把根到 S S S 路径上的节点都标记(设为 s i z e = 1 size=1 size=1),然后建立 f a i l fail fail 树( f a i l i → i fail_i o i faili→i),设 s i z e i size_i sizei 为 i i i 这个节点所代表的字符串在 S S S 中出现的次数。
那么在 f a i l fail fail 树中, i i i 的子树中的所有有效节点都能为 s i z e i size_i sizei 贡献 1 1 1。所以把每一个有效节点 s i z e size size 的初始值都设为 1 1 1 然后在 f a i l fail fail 树上从下往上统计 s i z e size size。
#include<bits/stdc++.h>
#define N 200010
#define ll long long
using namespace std;
struct Trie
{
int ch[26],fail;
ll size;
}t[N];
int n,node,id[N];
int cnt,head[N],nxt[N],to[N];
void adde(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
int insert(string s)
{
int u=0,len=s.size();
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
if(!t[u].ch[v]) t[u].ch[v]=++node;
u=t[u].ch[v];
}
return u;
}
void dfsTrie(string s)
{
int u=0,len=s.size();
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
u=t[u].ch[v];
t[u].size++;
}
}
void getfail()
{
queue<int>q;
for(int i=0;i<26;i++)
if(t[0].ch[i])
q.push(t[0].ch[i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(t[u].ch[i])
{
t[t[u].ch[i]].fail=t[t[u].fail].ch[i];
q.push(t[u].ch[i]);
}
else t[u].ch[i]=t[t[u].fail].ch[i];
}
}
for(int i=1;i<=node;i++)
adde(t[i].fail,i);
}
void dfsFail(int u)
{
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
dfsFail(v);
t[u].size+=t[v].size;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
string str;
cin>>str;
id[i]=insert(str);
}
getfail();
string s;
cin>>s;
dfsTrie(s);
dfsFail(0);
for(int i=1;i<=n;i++)
printf("%lld
",t[id[i]].size);
return 0;
}
/*
3
abc
cde
de
abcde
*/
2.https://blog.csdn.net/ez_lcw/article/details/99613063