📜  门|门 CS 1997 |第 39 题(1)

📅  最后修改于: 2023-12-03 14:58:34.964000             🧑  作者: Mango

题目介绍

本题为“门|门 CS 1997”的第39题,是AC自动机算法的一个练手题目,考察了算法的基本原理和实现方法。

题目描述

给定一个文本串S和多个关键字(模式串T1,T2,...Tn),对于每个模式串Ti(1<=i<=n),在S中查找Ti出现的次数。

输入格式

第一行为一个整数n,表示模式串的数量。

接下来n行,每行一个字符串,表示一个模式串。

接下来一行为一个字符串S,表示文本串。

输出格式

输出n行,每行一个整数,表示对应的Ti在S中出现的次数。

样例输入
3
he
world
hello
hello world! Here is a message from he. he says hello world!
样例输出
2
1
1
代码实现

以下是AC自动机算法的C++实现代码:

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 100010, M = 26 * N;

int n, tr[M][26], idx, fail[M], cnt[M];
char str[N], pat[N];

void insert(char *str) // 插入模式串
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int t = str[i] - 'a';
        if (!tr[p][t]) tr[p][t] = ++ idx;
        p = tr[p][t];
    }
    cnt[p] ++ ;
}

void build() // 构建AC自动机
{
    queue<int> q;
    for (int i = 0; i < 26; i ++ )
        if (tr[0][i]) q.push(tr[0][i]);

    while (q.size())
    {
        int t = q.front();
        q.pop();

        for (int i = 0; i < 26; i ++ )
        {
            int j = tr[t][i];
            if (!j) tr[t][i] = tr[fail[t]][i];
            else
            {
                fail[j] = tr[fail[t]][i];
                q.push(j);
            }
        }

        cnt[t] += cnt[fail[t]];
    }
}

void query(char *str) // 在文本串中查询模式串
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int t = str[i] - 'a';
        p = tr[p][t];
        for (int j = p; j && cnt[j] != -1; j = fail[j])
            cnt[j] = -1; // 已经匹配过,设为-1
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
    {
        scanf("%s", pat);
        insert(pat);
    }

    build();

    scanf("%s", str);
    query(str);

    for (int i = 1; i <= idx; i ++ )
        if (cnt[i] == -1)
            cnt[i] = 1;

    for (int i = 1; i <= n; i ++ )
        printf("%d\n", cnt[tr[0][pat[i - 1] - 'a']]);

    return 0;
}
思路分析

AC自动机算法,是一种高效的多模式匹配算法。与其他算法不同的是,AC自动机不需要对每个模式串都进行串匹配,而是将所有模式串构建成一棵自动机,然后通过自动机的状态转移进行匹配。其时间复杂度为O(n),其中n为文本串长度。

AC自动机的构建分为两步:Trie字典树的构建和自动机转移边的构建。对于每个模式串,我们将其插入Trie中,然后根据Trie的性质,根节点到每个模式串末尾节点的路径上的节点就是匹配该模式串的状态。对于每个节点,在AC自动机上都对应着一个状态,我们通过状态的转移机制实现各个模式串的匹配。

具体实现时,我们可以使用队列来维护每个状态的转移边,从而进行广度优先搜索。对于每个状态p,我们枚举它的所有出边,然后按照Trie的性质,找到它转移边所对应的状态q,并计算状态q的fail指针(即它实际匹配的后缀)。在搜索完成后,我们可以将每个状态的fail指针指向的状态的计数器加到该状态的计数器上,表示该状态匹配到的所有模式串的出现次数之和。

在查询时,我们按照文本串S的顺序,依次遍历每个字符,并按照自动机的状态转移机制,将每个状态转移到它匹配的状态。我们用一个计数器cnt,来记录每个状态在S中出现的次数。如果cnt的值为-1,说明该状态在之前已经匹配过,不需要再进行匹配。最后我们只需要输出每个模式串对应的状态在S中的出现次数即可。

参考文献
  1. [算法竞赛进阶指南] 例题8-8 多模式匹配