AC 自动机

AC 自动机的 fail 指针实质上表示 \(i\) 指向 \(j\) 表示 \(rt \rightarrow j\)\(rt \rightarrow i\) 的一个后缀。

首先可以确定某个点的 fail 深度 \(<\) 它自己。

假定当前所有深度 \(<\) 它自己的点都已经完成了 fail 的构建。

那么我们发现,当前 \(cur \rightarrow son\),若 \(cur\) 的 fail 节点 \(cur'\) 指向一个字符 \(c_{son'}=c_{son}\),那么就匹配上了后缀。

否则类似 KMP 那样往上跳,不断跳 fail,一直保证后缀性质进行匹配。

那么根据上一层必须全部完成 fail 构建,使用 bfs。

对于当前不存在的 \(tr_{cur,c}\)(字符 \(c\))直接把 \(cur\) 的 fail 处指向 \(c\) 的儿子接在 \(cur\) 后即可。

统计答案很简单,类似 KMP 跳 fail 即可。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int n, cnt, nxt[N], val[N], tr[N][26];

inline void ins(string str) {
    int len = str.size(), cur = 0;
    for (int i = 0; i < len; ++i) {
        int id = str[i] - 'a';
        if (!tr[cur][id]) tr[cur][id] = ++cnt;
        cur = tr[cur][id];
    }
    return ++val[cur], void();
}

inline void build() {
    queue <int> q;
    for (int i = 0; i < 26; ++i) if (tr[0][i]) q.push(tr[0][i]);
    while (!q.empty()) {
        int cur = q.front(); q.pop();
        for (int i = 0; i < 26; ++i) {
            if (tr[cur][i]) nxt[tr[cur][i]] = tr[nxt[cur]][i], q.push(tr[cur][i]);
            else tr[cur][i] = tr[nxt[cur]][i];
        }
    }
    return ;
}

inline int query(string str) {
    int len = str.size(), cur = 0, res = 0;
    for (int i = 0; i < len; ++i) {
        cur = tr[cur][str[i] - 'a'];
        for (int to = cur; to && ~val[to]; to = nxt[to])
            res += val[to], val[to] = -1;
    }
    return res;
}

int main() {
    ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
    cin >> n; string tmp;
    for (int i = 1; i <= n; ++i) cin >> tmp, ins(tmp);
    build(); cin >> tmp; return cout << query(tmp) << endl, 0;
}

对于要计算贡献的,可以拓扑优化 AC 自动机。

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;
int n, cnt, ed[N], siz[N], nxt[N], tr[N][26];
vector <int> g[N];

inline void ins(string str, int d) {
    int len = str.size(), cur = 0;
    for (int i = 0; i < len; ++i) {
        int id = str[i] - 'a';
        if (!tr[cur][id]) tr[cur][id] = ++cnt;
        cur = tr[cur][id];
    }
    return ed[d] = cur, void();
}

inline void build() {
    queue <int> q;
    for (int i = 0; i < 26; ++i)
        if (tr[0][i]) q.push(tr[0][i]);
    while (!q.empty()) {
        int cur = q.front(); q.pop();
        for (int i = 0; i < 26; ++i) {
            if (tr[cur][i]) {
                nxt[tr[cur][i]] = tr[nxt[cur]][i];
                q.push(tr[cur][i]);
            } else tr[cur][i] = tr[nxt[cur]][i];
        }
    }
    return ;
}

inline void calc(string str) {
    int len = str.size(), cur = 0;
    for (int i = 0; i < len; ++i)
        ++siz[cur = tr[cur][str[i] - 'a']];
    return ;
}

inline void dfs(int cur) {
    for (auto to: g[cur]) dfs(to), siz[cur] += siz[to];
    return ;
}

int main() {
    ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
    cin >> n; string str;
    for (int i = 1; i <= n; ++i) cin >> str, ins(str, i);
    build(); cin >> str; calc(str);
    for (int i = 1; i <= cnt; ++i) g[nxt[i]].push_back(i);
    dfs(0); for (int i = 1; i <= n; ++i) cout << siz[ed[i]] << endl;
    return 0;
}
posted @ 2023-02-20 11:12  MistZero  阅读(22)  评论(0编辑  收藏  举报