【AC自动机】AC自动机
Definition & Solution
AC自动机是一种多模式串的字符串匹配数据结构,核心在于利用 fail 指针在失配时将节点跳转到当前节点代表字符串的最长后缀子串。
首先对 模式串 建出一棵 tire 树,考虑树上以根节点为一个端点的每条链显然都对应着某一模式串的一个前缀子串,以下以树上的每个节点来代指从根节点到该节点对应的字符串。
定义一个字符串 \(S\) 在 trie 树上“出现过”当且仅当存在一条以根节点为一个端点的链,该链的对应字符串为 \(S\)。
考虑对每个节点求出一个 fail 指针,该指针指向在树上出现的该子串的 最长 后缀子串的端点。考虑在匹配文本串的时候,如果某一位置失配,最优的选择显然是跳转到被匹配串的最长后缀子串。因为这样所有在树上出现过的字符串都有机会被跳转到。
需要注意的是如果一个字符串匹配到了文本串,那么他的所有后缀子串都能匹配文本串。也就是说对于一个节点,他的fail,fail的fail,一直到根节点都能匹配当前文本串。
考虑求出fail指针的方法:
设根节点为空,显然根节点的所有孩子的fail指着指向根节点。
对于一个已经求出 fail 指针的节点 \(u\),设 \(u\) 的 fail 指向 \(w\),考虑 \(u\) 的一个孩子 \(v\),设 \(w\) 对应的孩子为 \(z\),且设 \(z\) 在 trie 树上是真实存在的。由于 \(w\) 是 \(u\) 的最长后缀子串,显然 \(w\) 的对应孩子 \(z\) 是 \(v\) 的最长后缀子串,于是直接将 \(v\) 的 fail 指向 \(z\) 即可。考虑如果 \(v\) 在 fail 上是不存在的,那么考虑一个 fail 指针指向 \(u\) 的节点,它对应 \(v\) 的指针显然应该指向 \(u\) 对应子串加上 \(v\) 代表字符后的最长真实存在的后缀子串。显然这个位置是 \(z\)。为了匹配时方便,我们直接将 \(u\) 的子节点指针指向 \(z\),这样在匹配 fail 指针指向 \(u\) 的节点时即对应第一种情况,正确性已经得到了证明。
于是一次 BFS 即可解决问题,对于 \(u\) 的子节点 \(v\) ,如果 \(v\) 真是存在,则将 \(v\) 的 fail 指针指向 \(u\) 的 fail 的对应节点,否则将 \(v\) 指向 \(u\) 的 fail 的对应子节点。
需要注意的是,如果一个节点再加上一个字符后在树上不存在任何一个后缀子串,那么该最长后缀为空,应该指向根节点。所以在初始化时,应该将所有节点的孩子和 fail 都指向根节点。
Samples
【P3808】AC自动机(简单版)
Description
给定 \(n\) 个模式串 \(S\) 和\(1\)个文本串 \(T\),求有多少个模式串在文本串里出现过。
Limitation
模式串总长度和文本串长度都不超过 \(10^6\)
Solution
考虑建出自动机后,在树上按照文本串匹配,注意到每匹配到一个节点,他的所有后缀子串都出现过,于是在每个节点都应该不断跳 fail 直到根,一路上的子串都标记为出现。
注意到本题只问有多少个串出现,而没有问每个串出现多少次,所以如果一个字符串已经在之前被跳到过了,他的所有后缀子串显然在之前也都已经被跳到过了,所以每跳到一个节点对该节点打一下标记,如果跳到过该节点了就直接break即可。
考虑一个节点最多会被跳一次,一共有 \(O(\Sigma|S|)\) 个节点,同时建立自动机的复杂度是 \(O(\Sigma|S|)\) 的,另外匹配文本串的复杂度是 \(O(|T|)\) 的,于是总时间复杂度 \(O(|T| + \Sigma|S|)\)
Code
#include <cstdio>
#include <cstring>
#include <queue>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
typedef long long int ll;
namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
}
template <typename T>
inline void qr(T &x) {
char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
}
namespace OPT {
char buf[120];
}
template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
int top=0;
do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
}
const int maxt = 26;
const int maxn = 1000009;
struct Tree {
Tree *son[maxt], *fail;
int endtime;
bool vis;
Tree(Tree *const _rt) : endtime(0), vis(false) {
for (auto &u : son) u = _rt;
fail = _rt;
}
Tree() : endtime(0), vis(false) {
fail = this;
for (auto &u : son) u = this;
}
};
Tree rot;
Tree *rt = &rot;
int n, ans, pcnt = 0;
char MU[maxn];
std::queue<Tree*>Q;
void makefail();
void ReadStr(char *s);
void query(const char *s);
void insert(const char *s);
int main() {
freopen("1.in", "r", stdin);
qr(n);
while (n--) {
ReadStr(MU); insert(MU);
}
makefail();
ReadStr(MU); query(MU);
return 0;
}
void ReadStr(char *s) {
do *s = IPT::GetChar(); while ((*s == ' ') || (*s == '\n') || (*s == '\r'));
do *(++s) = IPT::GetChar(); while ((~*s) && (*s != ' ') && (*s != '\n') && (*s != '\r'));
*s = 0;
}
void insert(const char *s) {
auto u = &rot;
while (*s) {
int k = *(s++) - 'a';
u = u->son[k] != rt? u->son[k] : u->son[k] = new Tree(&rot);
}
++u->endtime;
}
void makefail() {
for (auto u : rot.son) if (u != rt) {
Q.push(u);
}
while (!Q.empty()) {
auto u = Q.front(); Q.pop();
for (auto &v : u->son) {
auto k = &v - u->son;
if (v != rt) {
v->fail = u->fail->son[k];
Q.push(v);
} else {
v = u->fail->son[k];
}
}
}
}
void query(const char *s) {
auto u = &rot;
while (*s) {
u = u->son[*(s++) - 'a'];
for (auto v = u; v->vis == false; v = v->fail) {
v->vis = true;
ans += v->endtime;
}
}
qw(ans, '\n', true);
}
【P3706】AC自动机(加强版)
Description
给定 \(n\) 个模式串 \(S\) 和一个文本串 \(T\),\(S\) 可能在 \(T\) 中出现多次,求出现最多的是哪些模式串,出现了多少次。
Limitation
\(1~\leq~n~\leq~150\)
\(|S|~\leq~70,~|T|~\leq~10^6\)
Solution
暴力的想法显然是建出AC自动机然后每匹配到一个节点就暴力跳 fail,考虑本题与上一题的区别在于本题的模式串每出现一次就要统计一次,所以每个节点必须跳 fail 一直到根。考虑一个字符串 \(S\) 的后缀子串个数显然是 \(O(|S|)\) 的,匹配文本串的复杂度是 \(O(|T|)\) 的,于是总复杂度 \(O(|S||T|)\) 的。显然很不优秀。
考虑 AC 自动机的一个神奇性质:将所有的 fail 指针连成边,构成了一棵树。
证明:
考虑除了根节点以外每个点都有且仅有一个 fail 指针,根节点没有 fail 指针,这个条件等价于图上有 \(n-1\) 条边。
又由于 tire 树是联通的,所以该图满足 “联通”,“有 \(n-1\) 条边” 两个特性,根据树的判定定理可以证明这是一棵树。QED。
于是考虑跳 fail 一直到根将路径上的标记+1等价于将某个节点到根的链上所有点的标记整体加一,这个过程显然可以树形DP完成,于是每次在该节点打一个+1的标记即可。每个点的真实标记值为孩子的真是标记值之和加上该节点的标记值。
于是总复杂度 \(O(|T|~+~\Sigma|S|)\)
Code
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
typedef long long int ll;
namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
}
template <typename T>
inline void qr(T &x) {
char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
}
namespace OPT {
char buf[120];
}
template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
int top=0;
do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
}
const int maxm = 75;
const int maxn = 155;
const int maxt = 26;
const int maxL = 1000005;
struct Tree *rot;
struct Tree {
Tree *son[maxt], *fail;
std::vector<int>Endid;
std::vector<Tree*>tson;
bool vistag;
int vistime;
Tree() {
for (auto &u : son) u = rot;
fail = rot;
vistag = false;
vistime = 0;
}
~Tree() {
this->vistag = false;
for (auto u : son) if (u->vistag) delete u;
}
};
int n, maxv;
char MU[maxn][maxm], CU[maxL];
std::queue<Tree*>Q;
std::vector<int>ans;
void init();
void work();
void clear();
void print();
void buildfail();
void ReadStr(char *s);
void dfs(Tree *const s);
bool IsLet(const char *const s);
void Inserot(const char *s, const int id);
int main() {
freopen("1.in", "r", stdin);
qr(n);
while (n) {
clear();
init();
buildfail();
work();
print();
n = 0; qr(n);
}
return 0;
}
void clear() {
delete rot;
maxv = 0; ans.clear();
}
void init() {
rot = new Tree;
for (auto &u : rot->son) u = rot;
rot->fail = rot;
for (int i = 1; i <= n; ++i) {
ReadStr(MU[i]);
Inserot(MU[i], i);
}
}
void ReadStr(char *s) {
do *s = IPT::GetChar(); while (!IsLet(s));
do *(++s) = IPT::GetChar(); while (IsLet(s));
*s = 0;
}
inline bool IsLet(const char *const s) {
return (*s >= 'a') && (*s <= 'z');
}
void Inserot(const char *s, const int id) {
auto u = rot;
while (*s) {
int k = *(s++) - 'a';
u = u->son[k] != rot ? u->son[k] : u->son[k] = new Tree;
}
u->Endid.push_back(id);
}
void buildfail() {
for (auto u : rot->son) if (u != rot) Q.push(u);
while (!Q.empty()) {
auto u = Q.front(); Q.pop();
for (auto &v : u->son) {
auto k = &v - u->son;
if (v != rot) {
v->fail = u->fail->son[k];
Q.push(v);
} else {
v = u->fail->son[k];
}
}
}
for (auto &u : rot->son) if (u != rot) {
u->vistag = true; Q.push(u);
}
while (!Q.empty()) {
auto u = Q.front(); Q.pop();
u->fail->tson.push_back(u);
for (auto &v : u->son) if ((v != rot) && (v->vistag == false)) {
v->vistag = true; Q.push(v);
}
}
}
void work() {
ReadStr(CU);
auto s = CU;
auto u = rot;
while (*s) {
int k = *(s++) - 'a';
++((u = u->son[k])->vistime);
}
dfs(rot);
}
void dfs(Tree *const u) {
for (auto v : u->tson) {
dfs(v);
u->vistime += v->vistime;
}
if (u->Endid.size()) {
if (u->vistime > maxv) {
maxv = u->vistime;
ans.clear();
for (auto i : u->Endid) ans.push_back(i);
} else if (u->vistime == maxv) {
for (auto i : u->Endid) ans.push_back(i);
}
}
}
void print() {
std::sort(ans.begin(), ans.end());
qw(maxv, '\n', true);
for (auto i : ans) printf("%s\n", MU[i]);
}
【AC自动机】AC自动机的更多相关文章
- 后缀自动机/回文自动机/AC自动机/序列自动机----各种自动机(自冻鸡) 题目泛做
题目1 BZOJ 3676 APIO2014 回文串 算法讨论: cnt表示回文自动机上每个结点回文串出现的次数.这是回文自动机的定义考查题. #include <cstdlib> #in ...
- AC自动机(AC automation)
字典树+KMP 参考自: http://www.cppblog.com/mythit/archive/2009/04/21/80633.html ; //字典大小 //定义结点 struct node ...
- 【专题】字符串专题小结(AC自动机 + 后缀自动机)
AC自动机相关: $fail$树: $fail$树上以最长$border$关系形成父子关系,我们定一个节点对应的串为根到该节点的路径. 对于任意一个非根节点$x$,定$y = fa_{x}$,那$y$ ...
- HDU - 6208 The Dominator of Strings HDU - 6208 AC自动机 || 后缀自动机
https://vjudge.net/problem/HDU-6208 首先可以知道最长那个串肯定是答案 然后,相当于用n - 1个模式串去匹配这个主串,看看有多少个能匹配. 普通kmp的话,每次都要 ...
- BZOJ4032 [HEOI2015]最短不公共子串 【后缀自动机 + 序列自动机 + dp】
题目链接 BZOJ4032 题解 首先膜\(hb\) 空手切神题 一问\(hash\),二问枚举 三问\(trie\)树,四问\(dp\) 南二巨佬神\(hb\) 空手吊打自动机 \(orz orz ...
- bzoj 3796: Mushroom追妹纸 AC自动机+后缀自动机+dp
题目大意: 给定三个字符串s1,s2,s3,求一个字符串w满足: w是s1的子串 w是s2的子串 s3不是w的子串 w的长度应尽可能大 题解: 首先我们可以用AC自动机找出s3在s1,s2中出现的位置 ...
- BZOJ2754: [SCOI2012]喵星球上的点名(AC自动机/后缀自动机)
Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串 ...
- AC自动机&后缀自动机
理解的不够深 故只能以此来加深理解 .我这个人就是蠢没办法 学长讲的题全程蒙蔽.可能我字符串就是菜吧,哦不我这个人就是菜吧. AC自动机的名字 AC 取自一个大牛 而自动机就比较有讲究了 不是寻常的东 ...
- 字符串[未AC](后缀自动机):HEOI 2016 str
超级恶心,先后用set维护right,再用主席树维护,全部超时,本地测是AC的.放心,BZOJ上还是1S限制,貌似只有常数优化到一定境界的人才能AC吧. 总之我是精神胜利了哦耶QAQ #include ...
- HDU3065【AC自动机-AC感言】
Fourth AC zi dong ji(Aho-Corasick Automation) of life 9A(其实不止交了10发...) 感言: 一开始多组数据这种小数据还是...无伤大局,因为改 ...
随机推荐
- Python参数传递,既不是传值也不是传引用
面试的时候,有没有被问到Python传参是传引用还是传值这种问题?有没有听到过Python传参既不是传值也不是传引用这种说法?一个小小的参数默认值也可能让代码出现难以查找的bug? 如果你也遇到过上面 ...
- oraclejdbc
https://segmentfault.com/q/1010000004952621/a-1020000004955600
- R语言安装R package的2种方法
http://www.cnblogs.com/emanlee/archive/2012/12/05/2803606.html
- WebGL学习笔记七点一
第六章讲的是一些GL的一些语法,前面已经涉及,学习时直接跳过,来看第七章,第七章是真正意义的三维立体的出现,其实图形绘制方法是差不多的,就是Z坐标此时不再为0,所以很容易能构造出一些立体图形,但是立体 ...
- 我对git的认识
Git 真的是不了解 也没听说过git 所以真的不知道从何谈起 所以就参考度娘啦! Git是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本管理.Git 是 Linus To ...
- 编程之法section II: 2.2 和为定值的两个数
====数组篇==== 2.2 求和为定值的两个数: 题目描述:有n个整数,找出其中满足两数相加为target的两个数(如果有多组满足,只需要找出其中一组),要求时间复杂度尽可能低. 解法一: 思路: ...
- Hibernate连接数据库一直报NullPointerException
原来是少了这个.. //private HibernateTemplate hibernateTemplate; //少了下面 public HibernateTemplate getHibernat ...
- Codeforces Round #235 (Div. 2) D. Roman and Numbers 状压dp+数位dp
题目链接: http://codeforces.com/problemset/problem/401/D D. Roman and Numbers time limit per test4 secon ...
- iOS- Exception Type: 00000020:什么是看门狗机制
1.前言 前几天我们项目闪退之后遇到的一个Crash,之后逛了许多论坛,博客都没有找到满意的回复 在自己做了深入的研究之后,对iOS的看门狗机制有了一个基本的了解 而有很多奇怪的Cras ...
- Internet History, Technology and Security (Week 1)
Week 1 History: Dawn of Electronic Computing Welcome to Week 1! This week, we'll be covering the ear ...