AC自动机详解

第一步:构建Trie树

和普通的trie树构建方法一样
如:依次插入字符串apple、aply、cold、cool、cook
在这里插入图片描述

struct node {
    int fail;//失配指针
    int son[26];//儿子节点
    int end_num;//结尾标记
}trie[N];
int pos;//tire的指针
void build_trie(string s) {//建立字典树
    int now = 0;
    for (int i = 0; i < s.length(); i++) {
        int t = s[i] - 'a';
        if (!trie[now].son[t])trie[now].son[t] = ++pos;
        now = trie[now].son[t];
    }
    trie[now].end_num++;
}

第二步:构建fail

在这里插入图片描述

fail指针就是当前位置失配以后能够跳转继续进行匹配的字符位置,达到匹配过程中不需要回溯的效果。
构建过程使用了BFS:
假设当前节点为v, 是其父节点u的儿子
得到父节点的fail指针所指节点ptr_fail
遍历ptr_fail的子节点中是否有和v节点值相同的节点,若存在则v的fail节点指向该节点,不存在则继续遍历ptr_fail的子节点,直到找到与相同值的节点或达到root节点0

void get_fail() {//构建fail指针
    queue<int> q;
    for (int i = 0; i < 26; i++) {//首先预处理第一层的节点
        int t = trie[0].son[i];
        if (t) {
            trie[t].fail = 0;
            q.push(t);
        }
    }
    while (!q.empty()) {
        int u = q.front(); q.pop();//取出节点u
        for (int i = 0; i < 26; i++) {//枚举节点u的所有儿子
            int v = trie[u].son[i];//节点u的某个儿子v(实质上我们是构造v的fail指针)
            int ptr_fail = trie[u].fail;//节点u的fail指针所指节点(v节点父节点u的fail指针指向节点)
            if (v) {//如果子节点v存在的话
                //子节点v的fail指针指向 当前节点的fail指针所指向的节点的相同子节点(默认为0即跟根节点)
                trie[v].fail = trie[ptr_fail].son[i];
                q.push(v);
            }
            //否则当前这个儿子v指向 当前节点fail指针所指向的节点的相同子节点(默认为0即跟根节点)
            else trie[u].son[i] = trie[ptr_fail].son[i];
        }
    }
}

第三步:模式匹配

匹配的核心是从目标串从头逐个开始,在ac自动机中进行匹配,匹配上的则计数,若未匹配上则跳转失配位置进行尝试匹配,直到全部匹配完成

int query(string s) {
    int ans = 0;
    int now = 0;
    for (int i = 0; i < s.length(); i++) {//从目标串从头逐个开始,在ac自动机中进行匹配
        int t = s[i] - 'a';
        now = trie[now].son[t];
        //匹配上的则计数,若未匹配上则跳转失配位置进行尝试匹配,直到全部匹配完成
        for (int j = now; j && trie[j].end_num != -1; j = trie[j].fail) {
            ans += trie[j].end_num;
            trie[j].end_num = -1;//标记已统计过
        }
    }
    return ans;
}







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

#include<bits/stdc++.h>
#include <unordered_map>
using namespace std;
template<class...Args>
void debug(Args... args) {//Parameter pack
    auto tmp = { (cout << args << ' ', 0)... };
    cout << "\n";
}
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>pll;
typedef pair<int, int>pii;
const ll N = 1e6 + 5;
const ll MOD = 998244353;
const ll INF = 0x7fffffff;

struct node {
    int fail;//失配指针
    int son[26];//儿子节点
    int end_num;//结尾标记
}trie[N];
int pos;//tire的指针
void build_trie(string s) {//建立字典树
    int now = 0;
    for (int i = 0; i < s.length(); i++) {
        int t = s[i] - 'a';
        if (!trie[now].son[t])trie[now].son[t] = ++pos;
        now = trie[now].son[t];
    }
    trie[now].end_num++;
}
void get_fail() {//构建fail指针
    queue<int> q;
    for (int i = 0; i < 26; i++) {//首先预处理第一层的节点
        int t = trie[0].son[i];
        if (t) {
            trie[t].fail = 0;
            q.push(t);
        }
    }
    while (!q.empty()) {
        int u = q.front(); q.pop();//取出当前节点u
        for (int i = 0; i < 26; i++) {//枚举节点u的所有儿子
            int v = trie[u].son[i];//节点u的某个儿子v(实质上我们是构造v的fail指针)
            int ptr_fail = trie[u].fail;//节点u的fail指针所指节点(v节点父节点u的fail指针指向节点)
            if (v) {//如果子节点v存在的话
                //子节点v的fail指针指向 当前节点的fail指针所指向的节点的相同子节点(默认为0即跟根节点)
                trie[v].fail = trie[ptr_fail].son[i];
                q.push(v);
            }
            //否则当前这个儿子v指向 当前节点fail指针所指向的节点的相同子节点(默认为0即跟根节点)
            else trie[u].son[i] = trie[ptr_fail].son[i];
        }
    }
}
int query(string s) {
    int ans = 0;
    int now = 0;
    for (int i = 0; i < s.length(); i++) {//从目标串从头逐个开始,在ac自动机中进行匹配
        int t = s[i] - 'a';
        now = trie[now].son[t];
        //匹配上的则计数,若未匹配上则跳转失配位置进行尝试匹配,直到全部匹配完成
        for (int j = now; j && trie[j].end_num != -1; j = trie[j].fail) {
            ans += trie[j].end_num;
            trie[j].end_num = -1;//标记已统计过
        }
    }
    return ans;
}
int main() {
    ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);

    int n;
    cin >> n;
    string temp;
    for (int i = 0; i < n; i++) {
        cin >> temp;
        build_trie(temp);
    }
    get_fail();

    string text;
    cin >> text;
    cout << query(text) << "\n";


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值