后缀自动机详解

当想让学妹看博客时,怕旧的写的太烂被嫌弃,又怕新的看不懂……哎

一、定义:

单词的有向无环图

二、作用

从原点出发形成的所有路径即为单词的所有子串,并且通过维护endpos和endpos类,得知每个串出现的次数和出现的位置

三、构建后缀自动机

一些性质:

  1. endpos :数集,一些子串他们出现的位置相同,这些位置的集合称为endpos

  2. endpos类 :串集,一些子串他们出现的位置相同,这些子串的集合称为endpos类

  3. fail树

  • 树上节点的endpos是他父亲节点endpos的真子集

  • 树上节点的父亲包含的子串是当前节点包含的子串的后缀

  • 树上节点的endpos类中的子串长度连续

  • 树上节点的endpos类中的最小长度等于他父亲节点endpos类中的最长长度+1

后缀自动机维护的东西:

  1. fail树上的父亲

  2. c边连向的点

  3. 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;
		}

四、应用

  1. 如何求endpose集合的大小,每次新增加一个节点的时候说明出现了一个前缀,说明他会比他的父亲少一个endpose,初始化为1,在fail树上dp

  2. 如何求本质不同的子串的个数,l[nowNode] - l[fa[nowNode]]

  3. 求第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;
posted @ 2021-03-09 17:57  小又又yyyy  阅读(307)  评论(0编辑  收藏  举报