为什么说是简易版?
因为复杂度大概是(O(M*overline N)),而似乎还有另一种大概是(O(M+sum N))的。
不过据说比赛不会卡前一种做法,因为模式串一般不会很长。
那么步入正题。
对于(trie)树和(KMP)的预备知识就不多赘述了。
- 下个定义
对于(trie)树的每个节点维护一个(fail)指针。
我们当然可以感性把(fail)指针和(KMP)的失配函数连接起来,并认为它在 某节点在与文本串匹配失败后 指示应该跳到哪继续匹配 的东西。
对它的稍准确定义是:节点(i)的(fail)即为 (i)所在链(L_1) 在(trie)树 的 以二重子节点为头的 最长的 为其((L_1))后缀的链(L_2) 的尾节点位置。
解释一下,我们找到这样一条链(L_2),满足它为(L_1)的后缀(比如对(L_1)为abcd,bcd,cd,d都是),并且要求(L_2)的头节点为(trie)种深度为2的节点。此时,(fail)指向(L_2)的尾巴节点。
特别的:
- 当不存在这样的链时,(fail)指向根节点
- 对于节点(i)不存在的儿子(j),我们将它的位置信息等价于节点(i)的(fail)指针。(这与匹配有关)
- 下面说说两个操作
1.构建(fail)指针
我们尽可能的用之前的状态求解当前的状态,而(bfs)是一种层次遍历,我们使用(bfs)
当求解节点(i)的儿子(j)的(fail)指针:
若儿子(j)存在,把(i)的(fail)指针所指向位置 的儿子(j')(即(j)和(j')为同一个字符)的位置信息 作为(j)的(fail)指针指向(当然,可能(i)的(fail)指针根本不存在这样一个儿子,但是并没有影响)
否则,直接把儿子的位置信息指向(i)的(fail)的儿子(j')的位置信息。
参考代码:
void ac_build_fail()
{
for(int i=0;i<26;i++)
if(t[0].son[i])
q.push(t[0].son[i]);
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<26;i++)
if(t[now].son[i])
{
t[t[now].son[i]].fail=t[t[now].fail].son[i];
q.push(t[now].son[i]);
}
else
t[now].son[i]=t[t[now].fail].son[i];
}
}
2.进行匹配。
当匹配到文本串的位置(i)时,假使我们已经在前边找到了它在(trie)树的最深合法匹配,我们遍历由这个点引发的(fail)指针组成的一条链,直到找到根节点,当遇到单词末尾时,执行相应的操作。
然后我们向下寻找(i+1)是否存在,如果不存在,用被(fail)指针等效了的节点位置信息跳转。
参考代码:
void match()
{
int len=strlen(message),now=0;
for(int i=0;i<len;i++)
{
now=t[now].son[message[i]-'a'];
for(int j=now;j;j=t[j].fail)
//some operations
}
}
模板题:洛谷P3796 【模板】AC自动机(加强版)
题目描述
有(N)个由小写字母组成的模式串以及一个文本串(T)。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串(T)中出现的次数最多。
输入输出格式
输入格式:
输入含多组数据。
每组数据的第一行为一个正整数(N),表示共有(N)个模式串,(1≤N≤150) 。
接下去(N)行,每行一个长度小于等于(70)的模式串。下一行是一个长度小于等于(10^6)的文本串(T) 。
输入结束标志为(N=0) 。
输出格式:
对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。
参考代码:
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int N=1000010;
const int M=151;
char message[N],word[M][N];
int ans[M];
int n,cnt=0;
struct node
{
int fail,cnt;
int son[26];
}t[12000];
void add(int k)
{
scanf("%s",word[k]);
int now=0,len=strlen(word[k]);
for(int i=0;i<len;i++)
{
int word0=word[k][i]-'a';
if(!t[now].son[word0])
t[now].son[word0]=++cnt;
now=t[now].son[word0];
}
t[now].cnt=k;
}
queue <int > q;
void ac_build_fail()
{
for(int i=0;i<26;i++)
if(t[0].son[i])
q.push(t[0].son[i]);
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<26;i++)
if(t[now].son[i])
{
t[t[now].son[i]].fail=t[t[now].fail].son[i];
q.push(t[now].son[i]);
}
else
t[now].son[i]=t[t[now].fail].son[i];
}
}
void match()
{
int len=strlen(message),now=0;
for(int i=0;i<len;i++)
{
now=t[now].son[message[i]-'a'];
for(int j=now;j;j=t[j].fail)
ans[t[j].cnt]++;
}
}
int main()
{
scanf("%d",&n);
while(n)
{
cnt=0;
memset(t,0,sizeof(t));
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++) add(i);
ac_build_fail();
t[0].fail=0;
scanf("%s",message);
match();
int m_max=0;
for(int i=1;i<=n;i++)
m_max=max(m_max,ans[i]);
printf("%d
",m_max);
for(int i=1;i<=n;i++)
if(ans[i]==m_max)
printf("%s
",word[i]);
scanf("%d",&n);
}
return 0;
}
2018.5.22