题解 P3796 【【模板】AC自动机(加强版)】
CodyTheWolf · · 题解
开头小广告:自己做的一个模板库OwO
在这里HACK几乎所有的题解(因为好像没发现有对的?
4
AB
BC
CD
ABCD
ABCD
0
答案应该是
1
AB
BC
CD
ABCD
而其他同学都只输出了个别答案??
例如
1
AB
ABCD
或
1
AB
BC
CD
会出现上面的情况,只是因为没有判断串包含的关系或者是直接退回重新匹配了
嗷?什么意思??
举例:
- 当匹配完串
AB 时:- 如果写的自动机不退回到B并且重新匹配(从trie树的树根开始),而是继续匹配,那么会忽略掉
BC ,CD 两个答案,所以最终只会输出AB 和ABCD 这两个答案- 如果写的自动机退回到B重新匹配(从trie树的树根开始),那么
AB ,BC ,CD 都会匹配到,但是因为退回的关系,ABCD并没有被匹配到,这和上面的问题貌似有点矛盾了。上面的问题只是我自己遇到的,可能还会有其他情况,但是鉴于大多数题解并没有输出正确答案,这里给出一个我自己的解决方法:
- 其实罪魁祸首只是退回重新匹配的时候,没有匹配到最后重新匹配了,就像匹配AB时不继续往下匹配ABCD,而是跳回根匹配B。很显然我们只要让自动机匹配到最深节点,再往回跳就对了。
- 有的自动机直接把最深的节点下没有的节点指向了最深节点的fail
- 可能这行代码长这样:
trie[x][i] = trie[fail[x]][i]; - 这就是我们的判断依据了,如果下一个匹配字符是跳到fail的下面的话,说明下面已经没有节点,只是我们优化连了节点而已,这个时候就可以退回trie的根重新匹配了
即当
trie[x][c[i+1] - 'a'] == trie[fail[x]][c[i+1] - 'a'] 时注:c[i+1]是下一个匹配字符
最后,可能这个不是唯一解决的办法,或者说会有更好AC自动机写法可以避免这个问题。这篇题解的来源是我自己打模拟赛写的自动机跑不过像这样的例子,而我自己在luogu上打的模板却AC了,于是把题解都跑了一下发现都不太正确,在此纠正一下(也有可能是自己太傻XD)
CODE
有些地方有小注释
#include <iostream>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 1e6 + 5, MAXA = 26;
const int MAXP = 155, MAXLEN = 75;
int n;
queue<int> Q;
struct Automaton
{
char p[MAXP][MAXLEN];
int trie[MAXN][MAXA], fail[MAXN];
int num[MAXN], nxt[MAXN], tot;
signed id[MAXN][1], tail[MAXN];
bool tag[MAXN];
const int root = 0;
inline void Memset(void)
{
memset(trie, 0, sizeof trie);
memset(fail, 0, sizeof fail);
memset(num, 0, sizeof num);
memset(tag, 0, sizeof tag);
memset(tail, 0, sizeof tail);
return;
}
inline void Insert(int rak)
{
char *c = p[rak];
int x = root, len = strlen(c);
for (int i = 0; i < len; i++)
{
if (!trie[x][c[i] - 'a'])
trie[x][c[i] - 'a'] = ++tot;
x = trie[x][c[i] - 'a'];
}
tag[x] = true, nxt[x] = len - 1, id[x][++tail[x]] = rak;//nxt是长度-1,也是匹配到这个位置应该退回多少个字符
return;
}
inline void MakeFail(void)
{
for (int i = 0; i < MAXA; i++)
if (trie[root][i])
Q.push(trie[root][i]);
while (!Q.empty())
{
int x = Q.front();
Q.pop();
for (int i = 0; i < MAXA; i++)
{
if (trie[x][i])
{
fail[trie[x][i]] = trie[fail[x]][i];//这里是上面讲的优化
Q.push(trie[x][i]);
}
else
trie[x][i] = trie[fail[x]][i];//可以依据这个判断后面是否还有节点,解决本题解讨论的问题
}
}
return;
}
inline void Compare(char *c)
{
int ans = 0, x = root;
for (int i = 0, len = strlen(c); i < len; i++)
{
x = trie[x][c[i] - 'a'];
if (tag[x])
{
for (int j = 1; j <= tail[x]; j++)
ans = max(ans, ++num[id[x][j]]);
if (trie[x][c[i+1] - 'a'] == trie[fail[x]][c[i+1] - 'a'])//题解的核心!
i -= nxt[x], x = root;
}
}
printf("%d\n", ans);
for (int i = 1; i <= n; i++)
if (num[i] == ans)
printf("%s\n", p[i]);
}
} AC;
char s[MAXN];
signed main(void)
{
scanf("%d", &n);
while (n)
{
AC.Memset();
for (int i = 1; i <= n; i++)
scanf("%s", AC.p[i]), AC.Insert(i);
AC.MakeFail();
scanf("%s", s);
AC.Compare(s);
scanf("%d", &n);
}
return 0;
}