洛谷P3808 【模板】AC自动机(简单版) AC自动机

网址:https://www.luogu.org/problem/P3808

题意:

给定$n$个模式串和$1$个文本串,求有多少个模式串在文本串里出现过,相同的模式串需要多次计算。

题解:

$AC$自动机的模板题。$AC$自动机是基于$Trie$树的有限状态自动机,又称$Trie$图,$AC$自动机的结点的$fail$指针构造方法如下:在$Trie$树上,类似于$KMP$的$fail$指针,对于非空结点,找到其父亲的$fail$指针对应的结点的相同字母的儿子结点。对于空节点,直接把它连到其父亲的$fail$指针对应的结点的相同字母的儿子结点,根结点的儿子的$fail$指针指向根,形成转移图。然后查询的时候,我们要求的是求出现次数,所以我们当到达一个结点的时候,我们可以跳$fail$指针匹配其后缀,然后匹配过的加上标记防止重新匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <string>
#include <queue>
using namespace std;
#define maxn int(2e6+5)
int trie[maxn][26];
int cntword[maxn];
int fail[maxn];
int cnt = 0;
struct ac
{
    void insert(string& str)//插入字符串
    {
        int root = 0, next;
        for (int i = 0; i < str.size(); ++i)
        {
            next = str[i] - 'a';
            if (!trie[root][next])
                trie[root][next] = ++cnt;//将字符的位置按照放入顺序递增
            root = trie[root][next];
        }
        ++cntword[root];//该位置单词结束
    }
    void buildfail()
    {
        queue<int>que;
        for (int i = 0; i < 26; ++i)
        {
            if (trie[0][i])
            {
                fail[trie[0][i]] = 0;//根的儿子的fail一定指向根
                que.push(trie[0][i]);//将其放入队列
            }
        }
        while (!que.empty())
        {
            int now = que.front();//出队,以其为根
            que.pop();
            for (int i = 0; i < 26; ++i)
            {
                if (trie[now][i])
                {
                    fail[trie[now][i]] = trie[fail[now]][i];//fail指向其父节点的fail对应的相同子节点
                    que.push(trie[now][i]);
                }
                else
                    trie[now][i] = trie[fail[now]][i];//空节点是其父节点对应fail的相同儿子
            }
        }
    }
    int query(string& str)
    {
        int now = 0, ans = 0;
        for (int i = 0; i < str.size(); ++i)
        {
            now = trie[now][str[i] - 'a'];
            for (int j = now; j && cntword[j] != -1; j = fail[j])//匹配根到当前结点的子串的后缀以节省时间
            {
                ans += cntword[j];
                cntword[j] = -1;
            }
        }
        return ans;
    }
};
int main()
{
    string tmp;
    ios::sync_with_stdio(0);
    cin.tie(0);
    ac AC;
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i)
    {
        cin >> tmp;
        AC.insert(tmp);
    }
    fail[0] = 0;
    cin >> tmp;
    AC.buildfail();
    cout << AC.query(tmp) << endl;
    return 0;
}

  

AC代码:

 

posted @   Aya_Uchida  阅读(198)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示