您的位置:首页 > 其它

后缀自动机初探

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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  后缀自动机