HDU - 6096 String(这个ac自动机有、骚)

题意:给出n个串作为字典,和q个查询,每个查询包含一个前缀和一个后缀,问字典中有多少串与查询给出的前后缀匹配。

绝了这个做法。
ac自动机在失配的时候,会自动跳转到当前在ac自动机上跑的串能够匹配的最长后缀位置,这一条性质竟然能搞出这么骚的操作。

将查询的前后缀以后缀 + “{” + 前缀的形式拼在一起(之所以用大右括号是因为它是小写字母z的ascii码下一个),全部建立ac自动机,然后将字典串变成原串 + “{” + 原串的形式一个一个放到ac自动机上跑,随着失配,自然前半部分就会落到其在ac自动机中能够匹配的最长后缀位置,这就完成了后缀的匹配,之后一路跑下去,跨过大括号的部分自然也就完成了前缀的匹配。同时因为题目保证给出的查询前后缀一定比原串短,所以只能选取前后缀长度和比字典串小至少2的的查询串(前后缀各自最少会少一个字符)。
这个过程自然也能用fail树更新来优化复杂度(避免在跑的时候每次对一条链进行更新)。
真的nb这个做法。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 500005;

int t, n, m;

string in[100005];

struct AC_Automaton {
    int next[maxn][27];
    int fail[maxn];
    vector<int> endpos;
    int que[maxn], qt, qh;
    int ans[maxn], len[maxn];
    int sz, root;

    int newNode() {
        for (int i = 0; i < 27; i++) {
            next[sz][i] = -1;
        }
        len[sz] = ans[sz] = 0;
        fail[sz] = -1;
        return sz++;
    }


    void init() {
        sz = 0;
        qt = 0, qh = 1;
        endpos.clear();
        memset(ans, 0, sizeof(ans));
        root = newNode();
    }

    void add(string s) {
        int p = root, c;
        for (int i = 0; i < s.length(); i++) {
            c = s[i] - 'a';
            if (next[p][c] == -1) {
                next[p][c] = newNode();
                len[next[p][c]] = i + 1;
            }
            p = next[p][c];
        }
        endpos.push_back(p);
    }

    void getFail() {
        for (int i = 0; i < 27; i++) {
            if (~next[root][i]) {
                fail[next[root][i]] = root;
                que[++qt] = next[root][i];
            } else {
                next[root][i] = root;
            }
        }
        while (qh <= qt) {
            int p = que[qh++];
            for (int i = 0; i < 27; i++) {
                if (~next[p][i]) {
                    fail[next[p][i]] = next[fail[p]][i];
                    que[++qt] = next[p][i];
                } else {
                    next[p][i] = next[fail[p]][i];
                }
            }
        }
    }
    void solve(int x) {
        int p = root;
        for (int i = 0; i < in[x].length(); ++i) {
            int c = in[x][i] - 'a';
            p = next[p][c];
            while(len[p] > in[x].length() / 2 + 1){
                p = fail[p];
            }
            ++ans[p];
        }
    }

    void update(){
        for (int i = sz; i > root; --i) {
            int e = que[i];
            ans[fail[e]] += ans[e];
        }
    }

} ac;

int main() {
    ios::sync_with_stdio(0);
    cin >> t;
    while (t--) {
        cin >> n >> m;
        ac.init();
        for (int i = 1; i <= n; ++i) {
            cin >> in[i];
            in[i] += "{" + in[i];
        }
        string a, b;
        for (int i = 1; i <= m; ++i) {
            cin >> a >> b;
            ac.add(b + "{" + a);
        }
        ac.getFail();
        for (int i = 1; i <= n; ++i) {
            ac.solve(i);
        }
        ac.update();
        for (auto i : ac.endpos) {
            cout << ac.ans[i] << '\n';
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值