动手实现--AC自动机

Trie树:


把若干个单词按前缀合并就得到一棵树,这棵树称为Trie树。Trie树是有根树,每条边表示一个字符,每个节点表示一个从根到当前节点的唯一路径上的字符依次连接得到的字符串。由于空串是任何串的前缀,因此根就表示“空串”这个串。如何区分单词节点和非单词节点呢?插入单词的时候对每个节点mark一下即可。

KMP算法思想:

 能匹配就匹配,不能匹配就进行尽量小的平移来达到匹配。

有限自动机:


自动机是一个处理信息的机器,它的核心是状态和状态转移(和dp一样??),通过设计不同的状态和状态转移函数,来得到不同功能的自动机,因此自动机的应用非常广泛。

ac自动机


对字符串S构造一个这样的自动机:假设自动机扫描字符串T后处于状态w(w是一个整数,表示匹配长度),那么T的后w个字符是S的前缀,且w是满足这个性质的最大值。那么状态转移函数就可以这样定义:w + 字符c --> q,表示[Tc]的后q个字符是S的前缀,且这个q是满足这个性质的最大值。因此,状态转移矩阵很容易在O(m3Σ)的时间内求出来。

上述自动机慢在确定q需要花费O(m2)的时间,由kmp算法思想知道,如果S[w] == c,那么q = w + 1,否则w需要回退。那么我们利用kmp的回退数组(next数组),可以将复杂度降低到接近O(mΣ)。

同样,考虑多串的情形,则利用队列分层计算失配数组。这里会产生1个新的问题,假设当前匹配到了某个状态,这个状态表示的字符串为S,那么意味着不仅找到了S,而且找到了S的所有后缀,具体解决方法是给每个状态增加1个后缀链接,指向它的最大后缀单词,这样在找的时候要加速不少。

code(hdu2222,统计有多少模板串出现在了文本串里面):

#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define pb(x) push_back(x)
#define mp(x, y) make_pair(x, y)
#define all(a) (a).begin(), (a).end()
#define mset(a, x) memset(a, x, sizeof(a))
#define mcpy(a, b) memcpy(a, b, sizeof(b))
#define cas() int T, cas = 0; cin >> T; while (T --)
template<typename T>bool umax(T&a, const T&b){return a<b?(a=b,true):false;}
template<typename T>bool umin(T&a, const T&b){return b<a?(a=b,true):false;}
typedef long long ll;
typedef pair<int, int> pii;
#ifndef ONLINE_JUDGE
    #include "local.h"
#endif

int ans;

class ACAutomaton {
public:
    void clear() {
        memset(node, 0, sizeof(node));
        sz = 1;
    }
    void insert(char P[]) {
        int now = 0;
        for (int i = 0; P[i]; i ++) {
            int id = index(P[i]);
            if (!node[now][id]) node[now][id] = sz ++;
            now = node[now][id];
        }
        node[now].cnt ++;
        node[now].final_state = true;
    }
    void build() {
        queue<int> Q;
        for (int i = 0; i < SZ; i ++) {
            if (node[0][i]) {
                Q.push(node[0][i]);
            }
        }
        while (!Q.empty()) {
            int ch = Q.front(); Q.pop();
            for (int i = 0; i < SZ; i ++) {
                int next = node[ch][i];
                if (next) {
                    int now = node[ch].fail;
                    while (now && !node[now][i]) now = node[now].fail;
                    int buf = node[now][i];
                    node[next].last = node[next].fail = buf;
                    if (!node[buf].final_state) node[next].last = node[buf].last;
                    Q.push(next);
                }
            }
        }
    }
    void work(char T[]) {
        int now = 0;
        for (int i = 0; T[i]; i ++) {
            int id = index(T[i]);
            while (now && !node[now][id]) now = node[now].fail;
            now = node[now][id];
            find(i, now);
        }
    }
private:
    const static int N = 250007;
    const static int SZ = 26;
    struct Node {
        int next[SZ], fail, last;
        bool final_state;
        int cnt;
        int &operator[] (int p) { return next[p]; }
    };
    Node node[N];
    int sz;
    void find(int p, int now) {
        if (now == 0) return;
        if (node[now].final_state) process(p, now);
        find(p, node[now].last);
    }
    int index(char ch) {
        return ch - ‘a‘;
    }
    void process(int p, int now) {
        ans += node[now].cnt;
        node[now].final_state = false;
    }
};
ACAutomaton ac;
char s[100], t[1234567];

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
#endif // ONLINE_JUDGE
    int T, n;
    cin >> T;
    while (T --) {
        cin >> n;
        ac.clear();
        for (int i = 0; i < n; i ++) {
            scanf("%s", s);
            ac.insert(s);
        }
        scanf("%s", t);
        ac.build();
        ans = 0;
        ac.work(t);
        cout << ans << endl;
    }
    return 0;
}

  

时间: 2024-08-11 08:40:16

动手实现--AC自动机的相关文章

AC自动机:BZOJ 2434 阿狸的打字机

2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 1834  Solved: 1053[Submit][Status][Discuss] Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽

暑假集训day9补充(AC自动机)

推荐网站http://blog.csdn.net/niushuai666/article/details/7002823 AC自动机嘛,此AC(aho-corasick)非彼AC(Accepted). 我也不是很会解释 有一题是必须打的hdu2222. #include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int mn=

ac自动机基础模板(hdu2222)

In the modern time, Search engine came into the life of everybody like Google, Baidu, etc. Wiskey also wants to bring this feature to his image retrieval system. Every image have a long description, when users type some keywords to find the image, th

HDU 2825 Wireless Password AC自动机+dp

训练赛第二场的I题,上完体育课回来就把这题过了,今天训练赛rank1了,还把大大队虐了,而且我还过了这道题 (虽然我也就过了这道题...),第一次在比赛中手写AC自动机还带dp的,心情大好. 给一个字符串集合,求包含该集合超过K个字符的,长度为L的字符串的个数. 显然是在AC自动机上跑dp,设dp[u][L][k]表示当前在结点u,还要走L步,当前状态为k的个数.一开始第三维表示的是包含k个字符串,但是题目要求不含重复的,那就只能状压了.转移为dp[u][L][k]+=dp[v][L-1][nk

HDU 2896-病毒侵袭(ac自动机)

题意: 给定多个模式串,每给一个母串,输出包含模式串的编号,最后输出包含模式串的母串的数量. 分析: ac自动机模板 #include <map> #include <set> #include <list> #include <cmath> #include <queue> #include <stack> #include <cstdio> #include <vector> #include <st

hdu2222 Keywords Search &amp; AC自动机学习小结

传送门:http://http://acm.hdu.edu.cn/showproblem.php?pid=2222 思路:AC自动机入门题,直接上AC自动机即可. 对于构建AC自动机,我们要做的只有三件事: 1)构建字典树 2)构建失败指针 3)构建trie图(这道题好像不做这一步也能A...但是这一步不做是会被卡成O(n^2)的...) 1)第一步还是比较好理解的 根是虚根,边代表字母,那么根到终止节点的路径就是一个字符串,这样对于前缀相同的字符串我们就可以省下存公共前缀的空间. 加入一个模式

hdoj 2896 病毒侵袭(AC自动机)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2896 思路分析:题目为模式匹配问题,对于一个给定的字符串,判断能匹配多少个模式:该问题需要静态建树,另外需要对AC自动机的模板加以修改, 对于每个匹配的模式的最后一个单词的fail指针指向root,即可实现一个字符串进行多次模式匹配: 代码如下: #include <queue> #include <cstdio> #include <cstring> #include &

从Trie谈到AC自动机

ZJOI的SAM让我深受打击,WJZ大神怒D陈老师之T3是SAM裸题orz...我还怎么混?暂且写篇`从Trie谈到AC自动机`骗骗经验. Trie Trie是一种好玩的数据结构.它的每个结点存的是字母,因此得名`字母树`. 出一张图让大家感受下. (image powered by SaiBu NaoCu) 上面那是一棵插入了 ape,app,applicant,application,bake,ban,banana 等词的Trie.红色结点表示接受态. 显然,查找时只需顺着链照下来,插入只需

AC自动机

AC自动机 直接学AC自动机比较难理解,强烈建议先学完KMP和字典树并进行一定的练习后,对于失配指针和字典树构造有一定理解后再来学AC自动机的内容.有关AC自动机的详细介绍可见刘汝佳的<算法竞赛入门经典训练指南>P214. 给你一个字典(包含n个不重复的单词),然后给你一串连续的字符串文本(长为len),问你该文本里面的哪些位置正好出现了字典中的某一个或某几个单词?输出这些位置以及出现的单词. 这个问题可以用n个单词的n次KMP算法来做(效率为O(n*len*单词平均长度)),也可以用1个字典