自动机全家桶

  • 前言
  • 一、AC自动机
    • 1.优秀博客链接
    • 2.问题模板
    • 3.使用
    • 4.本质
    • 5.运用
    • 6.代码模板
  • 二、回文自动机(回文树)
    • 1.优秀博客链接
    • 2.问题模板
    • 3.使用
    • 4.本质
    • 5.运用
    • 6.代码模板
  • 三、后缀自动机(SAM)
    • 1.优秀博客链接
    • 2.问题模板
    • 3.使用
    • 4.本质
    • 5.运用
    • 6.代码模板

前言

在笔者平时的习题中,遇到了很多的比较复杂的字符串题目,而通常来说数据结构中的各种自动机是我们处理字符串题目的各种工具。由于笔者在学习了这些自动机之后对于每一种自动机的区别和使用范围,因此写下本篇博客用于整理自动机学习中的感悟和经验。

在学习自动机之前,有不少的前置知识点,如KMP、字典树Trie,如果是发现本篇博客中出现了无法理解的地方,可能需要考虑是否需要学习这些前置知识。

本篇博客中的代码模板仅供参考,笔者虽然都是用这些模板通过过一些题目,但仍然有出错的可能性,如有发现,欢迎指正 : )

一、AC自动机

1.优秀博客链接

AC自动机(详解)

AC自动机 算法详解(图解)及模板

2.问题模板

在一个很长的文本串T中,寻找S1~Sn字符串出现的次数

3.使用

对S1~Sn字符串建立自动机,用T逐字符进入状态机匹配

4.本质

带fail失配数组的字典树

5.运用

1)处理多个字符串的某种特征
2)寻找若干个有某种特征的字符串,通常特征字符串有相应的前缀 = 后缀
3)两组字符串间的匹配可以先分别建立两个自动机,然后用DFS函数同时遍历
4)AC自动机使用了Trie中闲置的空间建立失配连接,实际运用中需要注意区分实边和失配虚边
5)AC自动机同时拥有部分图和树的性质,解决问题的时候可以考虑灵活运用。同时可以结合图论算法和树论算法,如树上DP,DFS BFS搜索。

6.代码模板

喜闻乐见的板子环节

int tr[N][26], id;      // TRIE
int cnt[N];                //标记字符串结尾
int fail[N];             // fail指针void insert(string s)
{      //插入模式串int p = 0;for (int i = 0; i<s.size(); i++){int k = s[i] - 'a';if (!tr[p][k])tr[p][k] = ++id;p = tr[p][k];}cnt[p]++;
}
void build()
{queue<int> q;memset(fail, 0, sizeof(fail));for (int i = 0; i < 26; i++)if (tr[0][i])q.push(tr[0][i]);//首字符入队//不直接将0入队是为了避免指向自己while (!q.empty()){int k = q.front();q.pop();      //当前结点for (int i = 0; i < 26; i++){if (tr[k][i]){fail[tr[k][i]] = tr[fail[k]][i];      //构建当前的fail指针q.push(tr[k][i]);                     //入队}elsetr[k][i] = tr[fail[k]][i];//匹配到空字符,则索引到父节点fail指针对应的字符,以供后续指针的构建//类似并差集的路径压缩,把不存在的tr[k][i]全部指向tr[fail[k]][i]//这句话在后面匹配主串的时候也能帮助跳转}}
}
int query(string t)
{int p = 0, res = 0;for (int i = 0; i<t.size(); i++){p = tr[p][t[i] - 'a'];for (int j = p; j && ~cnt[j]; j = fail[j])res += cnt[j], cnt[j] = -1;}return res;
}

二、回文自动机(回文树)

1.优秀博客链接

Palindromic Tree——回文树【处理一类回文串问题的强力工具】
回文自动机专题

2.问题模板

求解一个或多个字符串中有关回文串种类和数量的各种问题

3.使用

对给定的字符串S建立自动机,然后根据遍历自动机来得到想要的各种信息

4.本质

一个特殊的Trie,但和Trie存在一定的区别,但根结点下有两个子节点,分别代表奇/偶数回文串,每一个结点都代表在父节点串的左右各添加一个字母

5.运用

1)求前缀S[0~i]中本质不同的回文串个数
2)S中每一种回文串出现的次数
3)求S中回文串总数
4)以下标i结尾的回文串个数
5)处理多个串的相同回文串数量时,可以写成S1 + '#' + S2 +...+ '#' + Sn连接成一个字符串放入自动机

6.代码模板

struct Trie
{static const int MAXN = 28;int nxt[N][MAXN], f[N], cnt[N], num[N], len[N], c[N], last, n, L;string nows[N];int newnode(int x){for (int i = 0; i < MAXN; ++i)nxt[L][i] = 0;cnt[L] = num[L] = 0;len[L] = x;return L++;}void init(){L = 0;newnode(0);newnode(-1);last = 0;n = 0;c[n] = -1;f[0] = 1;}int getf(int x){while (c[n - len[x] - 1] != c[n])x = f[x];return x;}void add(int x){x -= 'a';c[++n] = x;//c[i]表示第i次添加的字符int cur = getf(last);if (!nxt[cur][x]){int now = newnode(len[cur] + 2);f[now] = nxt[getf(f[cur])][x];nxt[cur][x] = now;num[now] = num[f[now]] + 1;}++cnt[last = nxt[cur][x]];}int gettypecount()      //回文串种类数{return L - 2;}void count(){for (int i = L - 1; i >= 2; --i)cnt[f[i]] += cnt[i];}void undo(){for (int i = 2; i <= L - 1; i++)cnt[f[i]] -= cnt[i];}void printcount()      //打印每一种回文串的数量{count();for (int i = 2; i < L; i++)cout << cnt[i] << ' ';cout << endl;undo();}void insertStr(string s)      //插入一个字符串{for (int i = 0; i < s.size(); i++)add(s[i]);}
} PT;

三、后缀自动机(SAM)

1.优秀博客链接

史上最通俗的后缀自动机详解 先看这篇准没错
学习笔记 后缀自动机
【学习笔记】后缀自动机SAM
【算法与数据结构】——后缀自动机

2.问题模板

解决一个字符串S(尤其是S的子串)的匹配问题

3.使用

对S建立自动机,之后可以通过遍历自动机来得到各种子串的信息

4.本质

用优化的手段建立一个Trie,其本质上是对S的所有的后缀字符串建立一个字典树,然后进行优化空间。同时保证从根结点出发无返回的遍历到自动机上的任意一点都是S中的子串。

5.运用

有点多,似乎很多题目都可以通过SAM来解决,这里也只介绍几个经典的用法,具体还是要花时间理解SAM的具体结构和逻辑含义。
1)判断一个串是不是S的子串
2)求解S中子串的种类数
3)求解两个字符串的最长公共子串

6.代码模板

int tot = 1, last = 1;
struct Node
{int len = 0, fa = 0; //len表示以该点为结尾的最长串长度int ch[26] = { 0 };
} node[N];
char str[N];
long long num[N];      // num数组代表子串出现次数
int h[N], e[N], ne[N], idx = 1;void extend(int c)
{int p = last, np = last = ++tot;num[tot] = 1;node[np].len = node[p].len + 1;for (; p && !node[p].ch[c]; p = node[p].fa)node[p].ch[c] = np;if (!p)node[np].fa = 1;else{int q = node[p].ch[c];if (node[q].len == node[p].len + 1)node[np].fa = q;else{int nq = ++tot;node[nq] = node[q], node[nq].len = node[p].len + 1;node[q].fa = node[np].fa = nq;for (; p && node[p].ch[c] == q; p = node[p].fa)node[p].ch[c] = nq;}}
}void add(int a, int b)
{e[++idx] = b, ne[idx] = h[a], h[a] = idx;
}void dfs1(int x)      //求每个集合中的子串的出现次数(每个点代表的集合中的所有子串出现的次数是相同的)
{for (int i = h[x]; ~i; i = ne[i]){dfs1(e[i]);num[x] += num[e[i]];}
}long long tynum;
void dfs2(int x)      //求子串种类数
{for (int i = h[x]; ~i; i = ne[i]){tynum += node[i].len - (node[node[i].fa].len + 1) + 1;dfs2(e[i]);}
}void querysub(int l, int r)      //对子串建立自动机
{for (int i = 0; i <= idx; i++)h[i] = -1, e[i] = ne[i] = 0;for (int i = 1; i <= tot; i++){auto& te = node[i];te.len = te.fa = 0;mem(te.ch, 0);}idx = 1;tot = last = 1;tynum = 0;for (int i = l - 1; i < r; i++)extend(str[i] - 'a');for (int i = 2; i <= tot; i++)      //绿边倒转建树add(node[i].fa, i);dfs1(1);      //出现的次数dfs2(1);      //子串的子串种类数
}int getcount(string s)      //某个字符串出现的次数
{int p = 1;for (int i = 0; i < s.size(); i++){int id = s[i] - 'a';if (!node[p].ch[id])return -1;elsep = node[p].ch[id];}return num[p];
}int lcs(string str)      //求最长公共子串
{int ans = 0, t = 0;int len = str.size();int p = 1;for (int i = 0; i < len; i++){int x = str[i] - 'a';if (node[p].ch[x]){p = node[p].ch[x];t++;}else{while (p && !node[p].ch[x])p = node[p].fa;if (p == 0){p = 1;t = 0;}else{t = node[p].len + 1;p = node[p].ch[x];}}ans = max(ans, t);}return ans;
}
void solve()
{mem(h, -1);scanf("%s", str);int q;scanf("%d", &q);while (q--){int le, ri;scanf("%d%d", &le, &ri);querysub(le, ri);printf("%lld\n", tynum);}
}

更多自动机未完待续…

作者:Avalon Demerzel

【数据结构】自动机全家桶(AC、回文、后缀自动机)相关推荐

  1. 简(kun)单(nan)到让我开(jue)心(wang)的后缀自动机全家桶(普通后缀、广义后缀、子序列)...

    目录 参考文献 后缀自动机是什么神仙玩意? 例题 right集合.等价类以及Parent树 定义 等价类性质 Trie?DAG? 自动机?自动鸡? 自动机的基本性质 一定存在后缀自动机吗? 后缀自动机 ...

  2. JZOJ5996. 【WC2019模拟2019.1.12】回文后缀

    题解 直接设答案fif_ifi​表示用字符集s构成长度为i的串,且不存在长度大于1的回文后缀. 相对的,设gig_igi​表示用字符集s构成长度为i的串,且存在长度大于1的回文后缀. 那么fi+gi= ...

  3. 【回文自动机】bzoj3676 [Apio2014]回文串

    回文自动机讲解!http://blog.csdn.net/u013368721/article/details/42100363 pam上每个点代表本质不同的回文子串.len(i)代表长度,cnt(i ...

  4. 回文树 / 自动机模板

    const int maxn = 400000; const int N = 26 ;struct Palindromic_Tree {int next[maxn][N] ;//next指针,next ...

  5. 常考数据结构与算法:最长回文子串

    暴力解法: 列举所有的子串,判断是否为回文串,保存最长的回文串. /** 暴力解法: 列举所有的子串,判断是否为回文串,保存最长的回文串.*/public int getLongestPalindro ...

  6. HihoCoder - 1441 后缀自动机一·基本概念(模拟,后缀自动机入门好题)

    题目链接:点击查看 题目大意:给出后缀自动机的定义以及各部分的参数,要求模拟后缀自动机的各个部分 题目分析:直接按照后缀自动机的定义模拟就好了,因为字符串的长度只有50,所以可以用n^3的算法暴力,还 ...

  7. hihocoder 1465 : 后缀自动机五·重复旋律8(后缀自动机+最长公共子串)

    1465 : 后缀自动机五·重复旋律8 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的 ...

  8. hihocoder 1457 : 后缀自动机四·重复旋律7(后缀自动机+拓扑序BFS)

    #1457 : 后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成 ...

  9. hihocoder 1449 : 后缀自动机三·重复旋律6(后缀自动机)

    #1449 : 后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数 ...

最新文章

  1. JK_Rush关于索引的一些总结
  2. 修改linux bash shell PS1
  3. python 输入正方形_Python打印正方形
  4. 内核网络输出帧的处理
  5. Eclipse如何不使用alt+/来实现自动提示
  6. 共性的缺失——由博客想到的
  7. python urllib编码
  8. 开源无线管理软件_开源无线电系统向育空地区及其他地区提供紧急警报和音乐
  9. 搜狐新闻数据400w+
  10. Windows Server搭建SAN存储服务(iSCSI)
  11. web服务器软件有哪些?带你快速了解
  12. 吴裕雄--天生自然 诗经:琵琶行
  13. Stratifyd入驻腾讯云市场,AI赋能品牌数字化升级
  14. 手机安装https证书报错!无法安装该证书,因为无法读取该证书文件解决办法
  15. 【渗透学习之基础篇】002网络安全法
  16. Linux ALSA驱动框架(六)--ASoC架构中的Platfrom
  17. 物联卡不激活多久过期,物联卡不激活多久会注销?
  18. python在屏幕上输出hello world_Python语言中,len(' hello world!')的输出结果为()。...
  19. [人工智能-深度学习-74]:环境 - Git、Github、Gitlab、Gitee区别与联系
  20. UPGRADE CATALOG

热门文章

  1. 2020 从0到1搭建个人博客网站,图解超详细!(附带软件资源)
  2. 计算机二级女生适合考什么东西,女生考哪些证书用途大最实用
  3. python比较excel中两列数据_python入门之对比两份excel表格数据
  4. Linux中脚本实现倒计时【两种方法:算术法和显示法】
  5. int *p() ; int (*p)();
  6. Jmeter中写入csv文件时保留 逗号 和 双引号
  7. 行为识别笔记:HOG,HOF与MBH特征
  8. 计算机语言的正交性,【zt】程序设计语言正交特性的一点思考
  9. 齁甜齁甜!用代码给女朋友每日定时推送睡前小故事
  10. 一本书掌握自动内存回收的机制《垃圾回收的算法与实现》(好书分享更新中)