自动机

AC自动机处理多串匹配——cf1202E

浪尽此生 提交于 2019-11-29 13:36:07
si+sj中间有一个切割点,我们在t上枚举这个切割点i,即以t[i]作为最后一个字符时求有多少si可以匹配,以t[i+1]作为第一个字符时有多少sj可以匹配 那么对s串正着建一个ac自动机,反着建一个自动机,然后t正反各匹配一次,用sum[]数组记录t[i]作为最后一个字符可以匹配的串数量 注意:求sum数组时,暴力跳fail显然会t,考虑到跳fail是为了统计匹配串的后缀,那么我们在build时,就可以在处理fail指针时就可以把那个fail的end加到now的end上去,这样就避免了暴力跳fail #include<bits/stdc++.h> using namespace std; #define N 200005 struct Trie{ int nxt[N][26],fail[N],end[N]; int root,L; int newnode(){ memset(nxt[L],-1,sizeof nxt[L]); end[L]=0; return L++; } void init(){ L++; root=newnode(); } void insert(char buf[]){ int len=strlen(buf+1); int now=root; for(int i=1;i<=len;i++){ if(nxt[now][buf[i]-'a']==-1) nxt

回文自动机的一点思考

断了今生、忘了曾经 提交于 2019-11-29 04:25:43
今年多校有这样一道题: 传送门 做法如果直接dfs暴力跳fail指针,跳到一个标记了的结点就break,跑得很快,后面有群友说只需要跳一次fail指针即可。 当时没怎么想明白,今天来看又想了一下,发现证明并不难,只需要分几种情况即可,我就直接放图吧: 总结: 在许多的自动机中都存在fail指针,并且结点与其指向结点通常含有包含关系,所以fail指针是求解子串问题强有力的工具。 平时我们暴力跳fail复杂度一般为 \(O(n^2)\) 的,但在回文自动机中,因为对称性,只需跳一次即可,复杂度就变为 \(O(n)\) 。 来源: https://www.cnblogs.com/heyuhhh/p/11452364.html

POJ2778 AC自动机 + 快速矩阵幂

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-29 00:45:53
http://poj.org/problem?id=2778 做法:利用AC自动机建矩阵之后进行N次矩阵乘 关于AC自动机配快速矩阵幂的理解: 1.题目限制10个字符串长度最多为10,那么建出的AC自动机的结点数至多为100 2.任意合法字符串必定通过nxt指针在AC自动机的结点之间转移 3.那么我们只要求出每次结点之间转移的数量,建立一个矩阵,就可以通过快速矩阵幂优化了 4.对于不合法的结点(病毒),将特定的转移次数设定为0即可。 5.注意不合法的结点除了插入的时候字典树上不合法的结点之外,所有fail指针指向不合法结点的及其后缀都是不合法结点,因为fail指针指向结点是该节点的后缀 #include <map> #include <set> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <bitset> #include <cstdio> #include <cstdlib> #include <cstring> #include <sstream> #include <iostream> #include <algorithm> #include <functional> using

CF G. Indie Album 广义后缀自动机+树链剖分+线段树合并

落花浮王杯 提交于 2019-11-28 15:51:40
Code: #include <map> #include <vector> #include <queue> #include <cstdio> #include <cstring> #include <algorithm> #define N 400003 #define setIO(s) freopen(s".in","r",stdin) using namespace std; char S[N]; int n,m,rt[N<<1]; namespace Trie { int id[N],tim; struct Node { map<int,int>ch; int siz,dfn,top,son,fa; }t[N]; void dfs1(int u) { t[u].siz=1; for(int i=0;i<27;++i) if(t[u].ch.count(i)) { int v=t[u].ch[i]; t[v].fa=u,dfs1(v),t[u].siz+=t[v].siz; if(!t[u].son||t[v].siz>t[t[u].son].siz) t[u].son=v; } } void dfs2(int u,int tp) { t[u].top=tp; t[u].dfn=++tim; if(t[u].son) dfs2(t[u].son,tp); for(int

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

半世苍凉 提交于 2019-11-28 15:01:58
目录 参考文献 后缀自动机是什么神仙玩意? 例题 right集合、等价类以及Parent树 定义 等价类性质 Trie?DAG? 自动机?自动鸡? 自动机的基本性质 一定存在后缀自动机吗? 后缀自动机是唯一的吗? 后缀自动机的几个性质 边的数量级 构造方法 代码 部分1 部分2 部分3 部分4 部分5 思路 代码 时间复杂度 广义后缀自动机 思路 练习 子序列自动机 参考文献 咕咕日报上的,就没有一个是差品: https://www.luogu.org/blog/Kesdiael3/hou-zhui-zi-dong-ji-yang-xie,同时,带luogu水印的图也是一律采用这个博客的,因为我太弱了,不会画图QAQ 。 时间复杂度的证明https://blog.csdn.net/qq_35649707/article/details/66473069 广义后缀数组就是在这学的: https://blog.csdn.net/litble/article/details/78997914 字符集的相关内容: https://oi.men.ci/suffix-automaton-notes/ 套B-树: https://www.cnblogs.com/hyghb/p/8445112.html stO 后缀自动机怎么能少了陈立杰大佬的论文了呢? Orz 博主是真的臭不要脸

AC自动机

倾然丶 夕夏残阳落幕 提交于 2019-11-28 08:39:50
AC自动机 AC自动机 概念 : 用于 多模式串与文本串匹配 ,也是字符串匹配算法之一。 方法就是把模板建成一个大的状态转移图,相当于 Trie树 + KMP 。 理解 : 匹配方法: 用模板串建立Trie树,给每个节点加上失配边也就是 fail标记 (用于失配后的转移,类似KMP中的next[ ]数组)。最后用文本串去匹配,一旦失配转移到fail标记的位置继续匹配。 fail指针的理解: 在Trie树中,每一个节点记录了从root到该节点的字符串,因为fail是为失配后的转移准备的,所以在给每个点确定fail指针指向时,我们就当在该节点失配了。 假设当前考虑的点为x。在匹配完 x 后失配了我们到底要将匹配位置转移到哪里呢?在考虑这个问题时我们不要忘了我们加上fail指针的目的,不加的话就相当于一段一段的匹配复杂度很高,所以我们是为了像KMP那样减少重复才加上fail指针的。 x点的fail指针指向其在trie树上存在的最长后缀上。 为什么这样: 因为当失配时最长后缀一定已经匹配完了,所以移动到最长后缀下一个位置,也就是fail指针所指下一个位置,这样就相当于继续顺着原位置继续匹配文本串。没有重复匹配已经匹配好的字符。 实现步骤: Trie树部分: net[26] :记录每个节点子节点有哪些字符,cnt:记录到这个点有多少个点结束,从第一个模板串开始建树,扫描每个字符

回文自动机常用操作和题目集

|▌冷眼眸甩不掉的悲伤 提交于 2019-11-28 01:32:26
回文自动机 以 i i i 为尾节点的字符串维护 增加一个节点后 即为在原有的所有真后缀回文串( m a r k [ f a i l [ n u m ] ] mark[fail[num]] m a r k [ f a i l [ n u m ] ] ) 增加一个回文串更新( mark[num] ) if ( ! tree [ last ] [ in [ i ] - 'a' ] ) { /*operation*/ mark [ num ] = mark [ fail [ num ] ] + 1 ; //统计以该节点为结尾的回文串个数 } //更新last cnt [ i ] = mark [ last ] ; 回文自动机(PAM) Interesting 本质不同的字符串总数 即为节点个数,抛去 1 号节点 mark [ i ] = num - 1 Palindromes and Super Abilities CA Loves Palindromic 维护本质不同的字符串(信息来自于子树) 对于一个本质不同的字符串 他的子树都可以访问他 标记所有字符串,统计他所有子树信息即可 由于编号越大,层数约深,所以倒着遍历即可 for ( int i = 1 ; i <= n ; i ++ ) { while ( in [ i - len [ last ] - 1 ] != in [ i ]

『后缀自动机和后缀树』

那年仲夏 提交于 2019-11-27 21:32:51
前言 本博客仅记录个人对后缀自动机的一些理解,没有入门详细推导等内容。 可以参考这两篇博客: 后缀自动机 (SAM) 学习笔记 后缀自动机 (SAM) 理解 后缀自动机到底记录了什么?由于一个字符串的任意一个子串可以表达为某个前缀的后缀这样的形式,所以后缀自动机其实以一种高度压缩的形式保存了字符串所有子串的信息。 一点性质,后缀自动机最多只有 \(2*n-1\) 个状态, \(3n-4\) 条转移,这可能在开数组大小,或者多组测试数据清空的时候要注意到。 回顾一些知识: \(1.\) \(SAM\) 的状态和转移构成了一张有向无环图,称为 \(DAWG\) 。 \(2.\) \(SAM\) 的状态和后缀链接构成了一棵树,称为 \(parent\) 树。 \(3.\) 后缀自动机有唯一一个起始节点 \(s\) ,代表空串,起始节点 \(s\) 到任意一个终止状态路径上的所有转移恰好可以表示原串的一个后缀,原串的每一个后缀同样都可以被表示。 \(4.\) 后缀自动机每一个状态本质上代表了一个 \(endpos\) 等价类,又称为 \(right\) 集合。 \(5.\) 引理: 两个非空子串 \(|u|\) 和 \(|w|\) (假设 \(|u|<|w|\) )的 \(endpos\) 相同,当且仅当字符串 \(u\) 是 \(w\) 的后缀。 \(6.\) 引理: 两个非空子串 \(

AC自动机 洛谷P3966 单词

匆匆过客 提交于 2019-11-27 21:25:32
题目链接: https://www.luogu.org/problem/P3966 题意:一篇论文是由许多个单词组成的,给出N个单词,查询每个单词在文章中出现的次数。 分析:其实还是裸的AC自动机。。。 构成文本串的时候直接在单词后面加一个特殊符号就行,查询函数遇到这个符号就重新从根开始,其他和上一篇一模一样。 insert,get_fail,topu函数都没有任何变化,因为我们插入的还是原本的单词,唯一有变化的只是文本串,所以只有用到文本串的查询函数变化了。 查询函数每次重头开始,就相当于所有模式串在按顺序来的一个个模式串里面匹配了。 #include<bits/stdc++.h> using namespace std; const int maxn=1e6+300;//单词间自行添加了符号,稍做扩大 const int inf=0x3f3f3f3f; typedef long long ll; #define meminf(a) memset(a,0x3f,sizeof(a)) #define mem0(a) memset(a,0,sizeof(a)); struct node{ int fail;//失配指针fail int vis[26];//子节点的位置,也就是字典树的那26个字母 int end;//如果是尾节点就记录 int ans;//用来记录出现次数 }AC