后缀自动机初探
2016-05-16 22:15
489 查看
定义
给定字符串S, S 的后缀自动机(SAM)是一个能够识别S 的所有后缀的自动机一些记号
trans(s,x):状态s走x转移到达的状态reg(s):状态s能接受的状态,即trans(s,str)属于end的所有str
功能
识别后缀:trans(init,str)属于end识别子串:trans(init,str)!=null
朴素实现
将所有后缀加入Trie树即可,但这样节点数是n^2的。一堆性质
对于一个状态,其本质属性是他能接受哪些字符串,即reg(s)。考虑一个原串的一个子串a,trans(init,a)=X,则reg(X)是一堆后缀的集合,这些后缀的性质是:若a出现在区间[l1..r1-1],[l2..r2-2],…中,则reg(X)={Suffix(r1),Suffix(r2),…}
定义right(X)={r1,r2,r3,…}其意义是串a在原串的这些位置出现,且仅在这些位置出现(右端点是这些位置),即出现位置的最大集合。
对于两个子串a,b,right(a)=right(b)等价于trans(init,a)=trans(init,b)。所以一个状态s识别的子串,由所有Right 集合是Right(s) 的字符串组成。
对于一个right集合Q,满足right(trans(init,str))=Q的str的长度是一段连续的区间[l..r],令Min(str)=l,Max(str)=r。因为如果长度越大,right集合就可能变小,长度越小,right集合就可能变大。
考虑两个状态a, b,它们的right 集合分别为Ra,Rb,假设Ra 与Rb 有交集,不妨设r属于ra交rb,若ra交rb不为空,则ra,rb是包含关系。
证明:不妨设a 中所有串都小于b 中所有串。因为都是由r 往前,所以a中串都是b 中串的后缀。所以Ra 是Rb 的真子集。
那么任意两个串的Right 集合,要么不相交,要么一个是另一个的真子集。
如果ra交rb不为空,则由于a 和b 表示的子串不会有交集,则[Min(a),Max(a)]与[Min(b),Max(b)] 不会有交集(一旦有交集,可证这两个集合全等)。
于是我们可以看出Right 集合实际形成了一个树的结构。不妨称为parent 树,在这个树中,叶节点的个数只有N 个,所以树的大小必然是O(n)的(因为每次父节点相当于是子节点集合的并),随着深度的减小,Max(s)的值在减小。
一个性质:Min(s)=Max(s->fa)+1,等价形式:Max(fa)=Min(s)-1
可以这么想:fa集合的每个元素向左扩展1位,得到新的一些字符串,这些字符串会按是否相等分成几组。因为要满足fa是s的并,就要小于等于s的最小值,又因为区间不能相交,就必须要小于。
另外注意:一个父节点的子节点的Min都是相等的(因为其子节点的最小值必须相等,不然就可以将最小值更大的作为最小值最小的子树,eg:len+2可以作为len+1的子节点)。Max值需要另求,暴力是O(n)的。
parent 树也是S 逆序的后缀树。可以求出这个子串出现的个数,就是所在状态Right 集合的大小。
parent 树里一个节点的Right 集合就是它所有儿子Right 集合的并集,也是dfs 序里连续的一段。同时在后缀自动机中某个点失配后,沿着parent指针可以到达一个新的状态,使得新的状态的后缀等于当前匹配的串的后缀。沿着后缀自动机的边走,right集合会变小,但并不是包含关系,因为位置向后移动了一个。
维护一个串在前面插入字符转移到哪个状态:后缀数组维护,二分新加串的所在的后缀数组的位置
构造后缀自动机
增量法:每次在串的末尾添加一个字符。对于Trie,BFS,last指针指向父亲即可建立np=trans(last,x),则对于p=last的向上的路径,如果没有x转移,将其连接到x,这里相当于是其集合走x转移转到了right集合={len+1}的集合。因为一个状态有x转移等价于right(此状态)中至少有一个元素有x转移。eg
aab???aab???aabx
0010000200003
{1,2,3}{2,3}{3}可以进行x转移,而{1},{1,2}不行。
如果找到了一个有x转移的节点p,令其trans(p,x)=q
若Max(q)=Max(p)+1,则可以将np并入q:np->pa=q
否则为保证right的正确性,需要建立新节点nq=right(q)并right(np),这样将q的转移信息复制给nq,将这一路向上的所有以q为x转移的节点,用nq代替即可。(只记录max值的原因:Max(fa)=Min(s)-1,其子节点的l都相等)
Code 建立后缀自动机
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; const int MAXN=1000,INF=0x3f3f3f3f; const int NS=26; struct node{ int ch[NS],pa,len; void init(int l=0){len=l;pa=0;memset(ch,0,sizeof(ch));} }; struct SAM{ node ns[MAXN+3]; int root,last,tot; int newnode(int l){ ns[++tot].init(l); return tot; } void init(){ tot=0;root=last=newnode(0); } void append(int x){ int p=last,np=newnode(ns[p].len+1); for(;p && ns[p].ch[x]==0;p=ns[p].pa)ns[p].ch[x]=np; if(p==0)ns[np].pa=root; else{ int q=ns[p].ch[x]; if(ns[q].len==ns[p].len+1)ns[np].pa=q; else{ int nq=newnode(ns[p].len+1); memcpy(ns[nq].ch,ns[q].ch,sizeof(ns[q].ch)); ns[nq].pa=ns[q].pa; ns[q].pa=ns[np].pa=nq; for(;p && ns[p].ch[x]==q;p=ns[p].pa)ns[p].ch[x]=nq; } } last=np; } int trans(int *s,int len,int init){ for(int i=0;i<len;i++){ if(ns[init].ch[s[i]]==0)return -1; init=ns[init].ch[s[i]]; } return init; } }sam;
相关文章推荐
- 【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416
- bzoj 2806: [Ctsc2012]Cheat
- 从最长公共子串到后缀自动机(LCS->SAM)
- SPOJ NSUBSTR
- bzoj-2780 Sevenk Love Oimaster
- bzoj-2555 SubString
- CF235C Cyclical Quest
- 【Vijos1382】【BZOJ1398】寻找主人 Necklace
- 【ZJOI2015】【BZOJ3926】诸神眷顾的幻想乡
- 【BZOJ3473】字符串
- 【JSOI2012】【BZOJ4327】玄武密码
- 【APIO2014】【BZOJ3676】回文串
- hdu 4622
- SPOJ 8222. Substrings(后缀自动机模板)
- bzoj 2946/Spoj 8222 后缀自动机
- HDU 4436 str2int 后缀数组 + 前缀和预处理 或 后缀自动机
- SPOJ LCS Longest Common Substring 后缀自动机
- SPOJ LCS2 Longest Common Substring II 后缀自动机
- SPOJ SUBLEX Lexicographical Substring Search 后缀自动机
- SPOJ NSUBSTR Substrings 后缀自动机