ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

L 语言 [AC自动机][状态压缩]

2022-06-10 22:32:20  阅读:152  来源: 互联网

标签:AC PAT int MAX 压缩 trie fail 自动机 节点


题目链接[洛谷]

题目大意:

给出一个大小为 n 的模式串集合 Sm 个目标串 t,找出每个 t 中能由 S 中任意个模式串(可重复使用)拼接成的最长前缀。

1 ≤ n ≤ 20,1 ≤ m ≤ 50,1 ≤  ∣s∣ ≤ 20,1 ≤ ∣t∣ ≤ 2×106

思路:

首先考虑一下我们用字典树是怎么匹配的。假设我们在目标串的某处走到了字典树的叶子节点,如果到目标串的此处是符合题意的前缀,那在此处回溯到目标串上对应于字典树根节点的位置,刚好是另外一次匹配的末尾位置。即 t[0 ... d1], t[(d1+1) ... d2], t[(d2+1) ... d3], ..., t[(dn-1 + 1) ... dn] 都能在字典树上找到匹配。

这样是找一次的复杂度就是 ∣t∣2 的,很明显需要优化。使用AC自动机,我们在目标串的每个位置上跳fail,如果能跳到字典树的某个叶子节点上,且减去模式串长度后刚好是上一次匹配的结束位置,那么这个位置就是新找到的答案。找一次的复杂度变为∣s∣∣t∣。可以写出这样的匹配代码:

vis[0] = true; // vis[目标串长度]为true表示是一个合法前缀
int p = 0;
int ans = 0;
for (auto c = &tar[1]; *c; ++c) // tar 是目标串
{
    p = trie[p][*c - 'a']; // 匹配当前字符
    for (int v = p; v; v = fail[v]) // 跳fail
    {
        if (exists[v]) // 此节点是trie叶子节点
        {
            vis[c - tar] |= vis[c - tar - len[v]]; // 刚好是之前匹配的结束位置,len[]是目标串长度,即字典树节点的深度
            if (vis[c - tar])
            {
                ans = c - tar; // 新答案
                break;
            }
        }
    }
}

此时我们可以发现,在完成自动机的构建之后,每个节点跳fail经过的地方都已经固定下来了,只要找到一个符合目标串长度的匹配,那么这个位置就能成为新答案。那能不能通过预处理的手段来优化掉跳fail的过程呢?假设我们把在trie上每个节点跳fail能找到的所有匹配目标串的长度都存起来,是不是就能快速计算呢?

再看看模式串的最大长度,只有20,则可以进一步尝试使用位运算来优化这个过程。

对于trie上的每一个节点,我们可以预处理出一个二进制数, 从低位数起,若第 i 位为1则表示此节点可以匹配到一个长度为 (i - 1) 的目标串。我们检查目标串时也会借鉴这个思路,我们用一个二进制数来表示目标串的每一个位置,从低位数起,若第 i 位为1则表示这个位置往前走 (i - 1) 步可以到达之前的一个合法前缀。也就是 f(i) = f(i - 1) << 1,且如果找到一个答案,我们就给 f(i) OR 1。

这样我们找一次的复杂度是∣t∣, 预处理fail跳转是的复杂度是 ∣s∣2n 。

实现

查看代码
#include <bits/stdc++.h>

using namespace std;
const int MAX_N = 20;
const int MAX_PAT_L = 20;
const int MAX_TAR_L = 2e6;
int trie[MAX_N * MAX_PAT_L + 1][26], cnt;
int len[MAX_N * MAX_PAT_L + 1];
bool exists[MAX_N * MAX_PAT_L + 1];
int fail[MAX_N * MAX_PAT_L + 1];

void insert(char str[])
{
    int p = 0;
    auto c = &str[0];
    for (; *c; ++c)
    {
        if (!trie[p][*c - 'a']) trie[p][*c - 'a'] = ++cnt;
        p = trie[p][*c - 'a'];
    }
    exists[p] = true;
    len[p] = c - str;
}

void build()
{
    queue<int> q;
    for (int c = 0; c != 26; ++c)
    {
        if (trie[0][c]) q.emplace(trie[0][c]);
    }

    while (q.size())
    {
        auto u = q.front();
        q.pop();

        auto f = fail[u];
        for (int c = 0; c != 26; ++c)
        {
            if (trie[u][c])
            {
                fail[trie[u][c]] = trie[f][c];
                q.emplace(trie[u][c]);
            }
            else trie[u][c] = trie[f][c];
        }
    }
}

char tar[MAX_TAR_L + 2];
int vis[MAX_N * MAX_PAT_L + 1];
int mat[MAX_TAR_L + 2];

int main(void)
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 0; i != n; ++i)
    {
        char bf[MAX_PAT_L + 1];
        scanf(" %s", bf);
        insert(bf);
    }

    build();

    for (int u = 1; u <= cnt; ++u)
    {
        for (int v = u; v; v = fail[v])
        {
            if (exists[v]) vis[u] |= 1 << (len[v]);
        }
    }

    while (m--)
    {
        scanf(" %s", tar + 1);
        mat[0] = 1;
        int p = 0;
        int ans = 0;
        for (auto c = &tar[1]; *c; ++c)
        {
            p = trie[p][*c - 'a'];
            int i = c - tar;
            mat[i] = mat[i - 1] << 1;
            if (mat[i] & vis[p]) mat[i] |= 1, ans = i;
        }

        printf("%d\n", ans);
    }

    return 0;
}

标签:AC,PAT,int,MAX,压缩,trie,fail,自动机,节点
来源: https://www.cnblogs.com/julic20s/p/16364783.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有