后缀自动机详解
当想让学妹看博客时,怕旧的写的太烂被嫌弃,又怕新的看不懂……哎
一、定义:
单词的有向无环图
二、作用
从原点出发形成的所有路径即为单词的所有子串,并且通过维护endpos和endpos类,得知每个串出现的次数和出现的位置
三、构建后缀自动机
一些性质:
-
endpos :数集,一些子串他们出现的位置相同,这些位置的集合称为endpos
-
endpos类 :串集,一些子串他们出现的位置相同,这些子串的集合称为endpos类
-
fail树 :
-
树上节点的endpos是他父亲节点endpos的真子集
-
树上节点的父亲包含的子串是当前节点包含的子串的后缀
-
树上节点的endpos类中的子串长度连续
-
树上节点的endpos类中的最小长度等于他父亲节点endpos类中的最长长度+1
后缀自动机维护的东西:
-
fail树上的父亲
-
c边连向的点
-
endpos类中最长串的长度
以上是一些概念,由他的性质和用途决定,就像1+1=2一样解释不了,早出生一点也许就不这样了呢
这些概念性的东西介绍完毕之后,就要开始讲如何维护了
考虑每次新添加一个字符进后缀自动机中(新开一个节点),改变的只有原串中的新字串的所有后缀节点,就相当于在原来的终止节点后添加一个字符的所有节点。
新开节点的长度+1,且每个终止节点都要向他引出一个c边,而由性质我们可以知道当前终止节点的所有祖先既终止节点集,当最终跳到源点的时候就表示,旧串中没有新形成的字符串的后缀,说明没有节点会发生改变,所以新节点的父亲指向1就可以了,那么如下代码的意义就解释完毕:
int preNode = lastNode;
int nowNode = lastNode = ++tot;
mac[nowNode].len = mac[preNode].len+1;
for(;preNode&&!mac[preNode].ch[c];preNode = mac[preNode].fa) mac[preNode].ch[c] = nowNode;
if (!preNode) mac[nowNode].fa = 1;
如果旧串中存在新形成的字符串的后缀呢?另tmpNode = ch[preNode][c],说明tmpNode为现在的终止节点,两种情况:len[tmpNode] = len[preNode]+1或者len[tmpNode] != len[preNode]+1
第一种情况说明tmpNode的endpose类中的所有字符串都是新串的后缀,把nowNode的父亲指向tmpNode
第二种情况说明tmpNode的endpose类中的字符串有一部分是新串的后缀,而这些串又是剩下那些串的后缀,为了方便维护,我们想到了拆点,把是后缀的一部分单独拎出来,把两部分分别维护的后缀自动机的内容都更新就可以了
突然发现最后一句让我也有点不理解的样子,但是经过我的深思熟虑,终于思考明白了:
为什么我会出现不等的情况,说明有其他的不经过终止节点的路径连向了这个点,并且拆分后的点的endpose类一个只包含那些路径所形成的字符串,一个只包含经过终止节点的路径所形成的字符串,因为第一种路径本来就连向tmpNode,所以我们只需要把第二种路径从指向tmpNode变成指向tmpNode1即可,代码如下:
int tmpNode = mac[preNode].ch[c];
if (mac[tmpNode].len == mac[preNode].len+1) mac[nowNode].fa = tmpNode;
else{
int tmpNode1 = ++tot;
mac[tmpNode1] = mac[tmpNode];
mac[tmpNode1].len = mac[preNode].len+1;
mac[tmpNode].fa = mac[nowNode].fa = tmpNode1;
for (;preNode&&mac[preNode].ch[c] == tmpNode;preNode = mac[preNode].fa) mac[preNode].ch[c] = tmpNode1;
}
四、应用
-
如何求endpose集合的大小,每次新增加一个节点的时候说明出现了一个前缀,说明他会比他的父亲少一个endpose,初始化为1,在fail树上dp
-
如何求本质不同的子串的个数,l[nowNode] - l[fa[nowNode]]
-
求第k大/小子串 弦论
五、广义后缀自动机
先建个Trie树然后在Trie树上添加新的节点和边来构建后缀自动机,不想说了,就这样吧
六、代码
void add(int c){
int preNode = lastNode;
int nowNode = lastNode = ++tot;
dp[nowNode] = 1;
mac[nowNode].len = mac[preNode].len+1;
for(;preNode&&!mac[preNode].ch[c];preNode = mac[preNode].fa) mac[preNode].ch[c] = nowNode;
if (!preNode) mac[nowNode].fa = 1;
else{
int tmpNode = mac[preNode].ch[c];
if (mac[tmpNode].len == mac[preNode].len+1) mac[nowNode].fa = tmpNode;
else{
int tmpNode1 = ++tot;
mac[tmpNode1] = mac[tmpNode];
mac[tmpNode1].len = mac[preNode].len+1;
mac[tmpNode].fa = mac[nowNode].fa = tmpNode1;
for (;preNode&&mac[preNode].ch[c] == tmpNode;preNode = mac[preNode].fa) mac[preNode].ch[c] = tmpNode1;
}
}
}
struct SAM{
int ch[maxn][30],len[maxn],fa[maxn],siz[maxn],tot;
void init(){
tot = 1;
memset(ch,0,sizeof(ch));
memset(len,0,sizeof(len));
memset(fa,0,sizeof(fa));
memset(siz,0,sizeof(siz));
}
void insert(string s){
int len = s.length(),root = 1;
for (int i = 0;i < len;i++){
int nxt = s[i]-'a';
if (!ch[root][nxt]) ch[root][nxt] = ++tot;
root = ch[root][nxt];
}
}
int add(int c,int lastNode){
int nowNode = ch[lastNode][c];
if (len[nowNode]) return nowNode;
len[nowNode] = len[lastNode] +1;
int preNode = fa[lastNode];
for (;preNode&&!ch[preNode][c];preNode = fa[preNode]) ch[preNode][c] = nowNode;
if (!preNode) fa[nowNode] = 1;
else{
int tmpNode = ch[preNode][c];
if (len[tmpNode] == len[preNode]+1) fa[nowNode] = tmpNode;
else{
int tmpNode1 = ++tot;
for (int i = 0;i < 26;i++){
if (!len[ch[tmpNode][i]]) continue;
ch[tmpNode1][i] = ch[tmpNode][i];
}
len[tmpNode1] = len[preNode]+1;
fa[tmpNode1] = fa[tmpNode];
fa[tmpNode] = fa[nowNode] = tmpNode1;
for (;preNode&&ch[preNode][c] == tmpNode;preNode = fa[preNode]) ch[preNode][c] = tmpNode1;
}
}
return nowNode;
}
void build(){
queue<pair<int,int> > q;
for (int i = 0;i < 26;i++){
if (ch[1][i]){
pair<int,int> x;
x.first = i,x.second = 1;
q.push(x);
}
}
while (!q.empty()){
pair<int,int> x = q.front();q.pop();
int lastNode = add(x.first,x.second);
for (int i = 0;i < 26;i++){
if (ch[lastNode][i]){
pair<int,int> tmp;
tmp.first = i,tmp.second = lastNode;
q.push(tmp);
}
}
}
}
}sam;