AC自动机复习笔记

  • AC自动机可以处理多模式串匹配问题。

问:事先给定若干个模式串(短一点的),多次询问一个文本串被多少个模式串匹配。

解:

AC自动机的建立过程:
①:把这若干个模式串插入到 t r i e trie trie树里面。
②:第二层点的 f a i l fail fail指针指向根 r t rt rt。再把第二层的点放进队列。
③:按照 b f s bfs bfs的顺序。拿出队列头的节点 x x x,把它存在的儿子的 f a i l fail fail指针设置为 x x x f a i l fail fail指针的那个儿子。把它不存在的儿子也赋值为 x x x f a i l fail fail指针的那个儿子。

处理询问的过程:
维护一个当前指针 c u r cur cur表示当前匹配到什么位置。枚举文本串的每一位, c u r cur cur就在 A C AC AC自动机上来回跳跃。一旦跳到一个点是原来串的末尾节点,就代表这个串被匹配上了。统计到答案里面。每次都不会重复。

这里要特别注意。如果一个位置被匹配上了,要一直向上根据fail跳到根。把这条路径(对应一个前缀)上的答案都统计上。因为AC自动机上的跳动可以跨越一些子树,不一定它的到根节点的路径上的答案都被统计过了。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 5, M = 105, K = 1e6 + 5;
struct poi {
    int ch[27], fail, cnt;
    vector<int> id;
}tr[N];
char bc[166][M], s[M], t[K];
int n, rt, tot, res[M], ans[N], sum, pp;
inline void insert(char *s, int id) {
    int len = strlen(s), cur = rt;
    for (int i = 0; i < len; ++i) {
        int &nxt = tr[cur].ch[s[i] - 'a'];
        if (!nxt) nxt = ++tot;
        cur = nxt;
    }
    tr[cur].id.push_back(id); tr[cur].cnt++;
}
queue<int> q;
inline void build_fail() {
    for (int i = 0; i < 26; ++i) {
        if (tr[rt].ch[i]) {
            tr[tr[rt].ch[i]].fail = rt;
            q.push(tr[rt].ch[i]);
        }
    }
    while (!q.empty()) {
        int x = q.front(); q.pop(); //printf("x = %d\n", x);
        for (int i = 0; i < 26; ++i) {
            if (tr[x].ch[i]) tr[tr[x].ch[i]].fail = tr[tr[x].fail].ch[i], q.push(tr[x].ch[i]);
            else tr[x].ch[i] = tr[tr[x].fail].ch[i];
        }
    }
}
inline void clear() {
    for (int i = 0; i <= tot; ++i) {
        for (int j = 0; j < 26; ++j) tr[i].ch[j] = 0;
        tr[i].id.clear();
        tr[i].fail = tr[i].cnt = 0;
    }
    tot = 0;
}
int main() {
    //freopen("data.in", "r", stdin);
    //freopen("data.out", "w", stdout);
    while (scanf("%d", &n) && n) {
        clear();
        for (int i = 1; i <= n; ++i) {
            scanf("%s", bc[i]);
            insert(bc[i], i);
        }
        //puts("qwq");
        build_fail();
        scanf("%s", t);
        int len = strlen(t), cur = rt;
        memset(ans, 0, sizeof ans);
        for (int i = 0; i < len; ++i) {
            cur = tr[cur].ch[t[i] - 'a'];
            int p = cur;
            while (p) {
                int up = tr[p].id.size();
                for (int j = 0; j < up; ++j)
                    ans[tr[p].id[j]] += tr[p].cnt;
                p = tr[p].fail;
            }
        }
        int p = 0;
        for (int i = 1; i <= n; ++i) {
            if (ans[i] > p) {
                p = ans[i];
                res[sum = 1] = i;
            }
            else if (ans[i] == p) res[++sum] = i;
        }

        printf("%d\n", p);
        for (int i = 1; i <= sum; ++i) printf("%s\n", bc[res[i]]);
    }
    return 0;
}

注意:AC自动机上是会有自环的。

所以,我们可以将失配指针指向的的节点理解为:
当前节点所代表的串,最长的、能与后缀匹配的,在Trie中出现过的前缀所代表的节点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值