Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >P5357「【模板】AC自动机(二次加强版)」

P5357「【模板】AC自动机(二次加强版)」

作者头像
hotarugali
发布于 2022-03-02 12:18:12
发布于 2022-03-02 12:18:12
61800
代码可运行
举报
运行总次数:0
代码可运行

1. 题目

题目链接:P5357「【模板】AC自动机(二次加强版)」

题目描述

给你一个文本串 Sn 个模式串 T_{1..n},请你分别求出每个模式串 T_i​ S 中出现的次数。

输入格式

第一行包含一个正整数 n表示模式串的个数。

接下来 n 行,第 i行包含一个由小写英文字母构成的字符串 T_i​

最后一行包含一个由小写英文字母构成的字符串 S

数据不保证任意两个模式串不相同。

输出格式

输出包含 n行,其中第 i行包含一个非负整数表示 T_iS中出现的次数。

输入输出样例

输入 #1
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
5
a
bb
aa
abaa
abaaa
abaaabaa
输出 #1
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
6
0
3
2
1

说明/提示

1 \leq n \leq 2 \times 10^5T_{1..n}的长度总和不超过2 \times 10^5S 的长度不超过 2 \times 10^6

2. 题解

分析

  • 普通的查询显然不行(TLE 一片),于是需要考虑如何优化普通的查询。普通的查询导致 TLE 主要原因在于跳 fail指针时递归的跳,对于类似aaaa \cdots aaaa 的字符串相当于每向前查找一个字符就需要递归跳 fail 指针,而每次跳 fail 只导致深度减 1,最终导致最坏的时间复杂度为 O(n*m)(其中 n 为主串长度,m为模式串长度)。
  • 由于整个字典树是确定的,fail 指针也是确定的,就是说:一个结点如果被更新了,那么字典树中能够匹配到该节点对应字符串的真后缀的结点都是确定的(即递归跳 fail 到达的结点),在递归跳 fail 过程中也一定会被更新。既然如此于是我们可以不用那么着急更新所有结点,而是考虑对匹配到最长串的结点打上标记,最后处理完后统一递归跳 fail 更新所有能够匹配到的结点。
  • 匹配完整个主串后,所有匹配到的最长串的结点都被打上了标记。那此时应该从那些结点开始递归跳 faill 指针呢?注意到,递归跳 fail指针的过程本质上是从树的叶结点走到根结点的过程,这里的树指的是依靠 fail指针构建的有向树,根结点就是字典树的根结点(因为fail[0] = 0)。于是,对于 fail指针构建的有向树而言,其叶结点的入度为 0,出度为 1(一个结点的 fail指针指向的位置是固定且唯一的),而我们首先要处理的就是所有叶结点,然后才是叶结点指向的父结点,即将父结点的所有入边关联的子结点处理完后才处理父结点,以此类推,直到根结点,而这正是拓扑排序的顺序。

根据这种思路优化后的最坏复杂度降到了 O(n+m)

代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <bits/stdc++.h>
using namespace std;

// AC 自动机
struct Automaton {
    #ifndef _AUTOMATON_
    #define ll int
    #define MAXN 400005
    #define MAXCHAR 26
    #endif
    ll cnt; 
    ll trie[MAXN][MAXCHAR];
    ll exist[MAXN];
    ll fail[MAXN];
    ll mark[MAXN];          // 模式串编号
    ll in[MAXN];            // 入度
    Automaton(): cnt(0) {}
    // 插入字符串
    void insert(char *str, ll i) {
        ll p = 0;
        for(ll i = 0; str[i]; ++i) {
            ll c = str[i] - 'a';
            if(!trie[p][c]) {
                trie[p][c] = ++cnt;
            }
            p = trie[p][c];
        }
        mark[i] = p;
    }
    // 构建失配指针
    void build() {
        queue <ll> q;
        for(ll i = 0; i < MAXCHAR; ++i) {
            if(trie[0][i]) {
                q.push(trie[0][i]);
            }
        }
        // BFS 
        while(q.size()) {
            ll p = q.front();
            q.pop();
            for(ll i = 0; i < MAXCHAR; ++i) {
                if(trie[p][i]) {
                    fail[trie[p][i]] = trie[fail[p]][i];
                    ++in[trie[fail[p]][i]];
                    q.push(trie[p][i]);
                } else {
                    trie[p][i] = trie[fail[p]][i];
                }
            }
        }
    }
    // 匹配主串
    void match(char *str) {
        ll p = 0;
        for(ll i = 0; str[i]; ++i) {
            ll c = str[i] - 'a';
            ll u = trie[p][c];
            ++exist[u];
            p = trie[p][c];
        }
    }
    // 计算所有模式串出现次数
    void solve() {
        queue <ll> q;
        for(ll i = 1; i <= cnt; ++i) {
            if(!in[i])  q.push(i);
        }
        while(q.size()) {
            ll p = q.front();
            q.pop();
            if(fail[p]) {
                exist[fail[p]] += exist[p];
                --in[fail[p]];
                if(!in[fail[p]])    q.push(fail[p]);
            }
        }
    }
};


int main()
{
    ll n;
    static char str[MAXN*10];
    static Automaton ac;
    scanf("%d", &n);
    for(ll i = 0; i < n; ++i) {
        scanf("%s", str);
        ac.insert(str, i);
    }
    scanf("%s", str);
    ac.build();
    ac.match(str);
    ac.solve();
    for(ll i = 0; i < n; ++i) {
        printf("%d\n", ac.exist[ac.mark[i]]);
    }
    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
AC自动机
AC 自动机基于字典树结构,将所有模式串插入字典树中,然后对字典树中的每个结点构造失配指针。AC 自动机中的失配指针与 KMP 中不同的是,AC 自动机中的失配指针是相对于整棵字典树的,即失配指针不再是局限于当前模式串,而是对于整棵字典树中所有的模式串而言的。
hotarugali
2022/03/02
1K0
字符串-AC自动机(详细图解)
Fail指针 同KMP的next一样,Fail指针是AC自动机的核心,是在树上指出失配后下一个跳转的位置,而不用全部回溯,大大减少时间。那么Fail是怎么跳转的? 以HDU-2222的样例为例说明,模式串P={“she”,“he”,“say”,“shr”,“her”},文本串S=“yasherhs”。
唔仄lo咚锵
2020/10/09
1.4K0
字符串-AC自动机(详细图解)
简单ac自动机学习
简单的来讲解一下自动机,虽然都在说这是和的组合,但个人觉得不需要深入理解这两个仍然可以学会它。同时由于网上已经存在了很多教程,本文尽量避免采取和网上一样的说法,加入一点自己在学习这个东西的时候的思考和理解,当然可能会存在错误,欢迎评论区指出。
ACM算法日常
2019/12/12
5660
简单ac自动机学习
KMP与AC自动机详细讲解(带图)
KMP​ 算法可以说是我学过的算法里最让我印象深刻的一个算法了。初学 KMP​​ 的时候真的是抓耳挠腮,硬啃了一下午的博客才勉强可以自己独立推一遍算法的整个流程。第二次学习 KMP​ 是为了在数据结构课上给同学们介绍这个算法,自己学和教会别人又是不一样的难度,于是我又重新学习了一遍,但这一次学习时有很多之前觉得很抽象的东西都突然茅塞顿开了,为了讲解的效果,我还反复推导了几次算法,确保讲课的流畅。第三次学习 KMP​ 是为了给集训队的学弟们讲这个算法,而竞赛更偏重于算法的应用,所以我在重新推演了一次算法后又找了一些经典例题。自此,对于 KMP 的理解可以说是挺明晰了。最近,我又学习了 AC自动机,很巧的是,AC自动机的思想和 KMP 是一样的,于是我又“被迫”重温了一遍 KMP ,既然那么有缘分,不如就写篇博客吧。
Here_SDUT
2022/08/11
1.1K0
KMP与AC自动机详细讲解(带图)
hdu 2222 AC 自动机 模版(数组实现)
t个样例,n个单词,一个文本串,求文本串中单词出现的次数。 若给出单词ab,ab 文本ab,匹配数为2
用户2965768
2019/08/01
3170
AC 自动机详解
Trie 是一种能够快速插入和查询字符串的多叉树结构。节点的编号各不相同,根节点编号为0,其他节点用来标识路径还可以标记单词插入的次数。边表示字符。
浪漫主义狗
2023/02/18
1.2K0
AC 自动机详解
病毒侵袭 HDU - 2896 AC 自动机
#include <queue> #include <cstdlib> #include <cmath> #include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 1e5+10; int trie[maxn][95]; //字典树 //int cntwor
用户2965768
2019/08/01
3880
AC自动机和Fail树
Fail指针的基本性质:某只结点的Fail指针,指向它所代表的字符串的最长的后缀的结点。
全栈程序员站长
2022/07/23
7420
POJ - 2778 DNA Sequence AC自动机+矩阵快速幂 经典好题
邻接矩阵A = mp[i][j]表示从 i 到 j 的方案数, A^n 表示点与点之间走n步到达的方案数。
用户2965768
2019/08/01
5640
P3796 【模板】AC自动机(加强版)
题目描述 有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。 输入输出格式 输入格式:输入含多组数据。 每组数据的第一行为一个正整数N,表示共有N个模式串, 。 接下去N行,每行一个长度小于等于70的模式串。下一行是一个长度小于等于 的文本串T。 输入结束标志为N=0。 输出格式: 对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。 输入输出样例 输入样例#1:
attack
2018/04/12
5970
字符串-回文自动机
回文自动机(Palindromes_Automaton,PAM),也叫回文树,是高效解决回文问题的算法,能够解决很多Manacher算法解决不了的回文题。可以解决如回文串个数、本质不同回文串个数、前缀0-i内回文串个数、某下标结尾的回文串个数等。
唔仄lo咚锵
2021/12/31
9830
字符串-回文自动机
洛谷P3808 【模板】AC自动机(简单版)
subtask1[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6,n=1;
attack
2018/07/27
6580
HDU - 2243 考研路茫茫——单词情结 AC自动机+矩阵快速幂
数据范围很大,总共有 26 + 26^1 + 26^2 + ....... + 26^n种可能,减去不包含词根的情况就行。
用户2965768
2019/08/01
3590
AC自动机总结「建议收藏」
由于大连现场赛的一道 AC自动机+ DP的题目(zoj3545 Rescue the Rabbit)被小媛同学推荐看 AC自动机。经过一段时间的努力,终于把 shǎ崽神牛的 AC自动机专辑题目 AK(其实还差那个高中题。。囧。。不让做)。
全栈程序员站长
2022/07/22
5170
AC自动机总结「建议收藏」
P2580「于是他错误的点名开始了」
他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛 CON900)。
hotarugali
2022/03/01
7730
字典树
字典树(Trie)用边来代表字母,从根结点到树上某一结点的路径就代表了一个字符串。
hotarugali
2022/03/02
4700
字典树
回文自动机、AC自动机和后缀自动机介绍(2)
 AC自动机,有的地方也叫Trie图,可以用来解决多串匹配的问题  多串匹配是这样一个问题:给定N个敏感词W1, W2, W3, … WN,然后对于一个字符串S,判断S中存在不存在任意敏感词  多串匹配比较常见的算法时间复杂度都比较高。比如我们可以用多次KMP,伪代码如下:
mathor
2018/07/24
1.9K0
回文自动机、AC自动机和后缀自动机介绍(2)
AC 自动机_模式匹配自动机
学习AC自动机的前提是要会trie数和KMP字符串匹配, 它的功能是能对好多个模式串进行同时查找。
全栈程序员站长
2022/11/17
5390
P3808 【模版】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模版题。 用于检测正确性以及算法常数。 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交。 题目描述 给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。 输入输出格式 输入格式: 第一行一个n,表示模式串个数; 下面n行每行一个模式串; 下面一行一个文本串。 输出格式: 一个数表示答案 输入输出样例 输入样例#1: 2 a aa aa 输出样例#1: 2 说明 也是模板,没什么么好说的, 只不过每次更新的时候是++,而
attack
2018/04/12
6390
洛谷P3796 【模板】AC自动机(加强版)
题目描述 有 NN 个由小写字母组成的模式串以及一个文本串 TT 。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串 TT 中出现的次数最多。 输入输出格式 输入格式: 输入含多组数据。 每组数据的第一行为一个正整数 NN ,表示共有 NN 个模式串, 1 \leq N \leq 1501≤N≤150 。 接下去 NN 行,每行一个长度小于等于 7070 的模式串。下一行是一个长度小于等于 10^6106 的文本串 TT 。 输入结束标志为 N=0N=0 。 输出格式: 对于每组数据,第一
attack
2018/07/04
3580
相关推荐
AC自动机
更多 >
LV.1
这个人很懒,什么都没有留下~
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档