ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

后缀自动机

2022-06-04 23:32:49  阅读:149  来源: 互联网

标签:子串 每个 SAM 后缀 len 集合 自动机 节点


(本文不适合初学者)

SAM

  • 个人认为 SAM yyds

  • 希望有一天 SAM 能统治字符串界

前置概念

  • \(\operatorname{endpos}\) 集合表示一个子串在原串中出现的位置集合

  • 所有的子串通过 \(\operatorname{endpos}\) 分成一个个等价类

构造

  • 每个节点代表一个子串集合(或者看成是一种状态),状态之间有一些字符转移边

  • 每个节点维护集合最长的字符串的长度

  • 每个节点维护的子串集合实际上就是集合中的最长子串的一些前缀

  • 每个节点维护每个字符转移边的节点

  • 每个节点维护后缀链接指向集合中的最长子串的最长的和这个子串的 \(\operatorname{endpos}\) 不一样的后缀所在的节点,并且满足 \(\operatorname{minlen(x)}=\operatorname{maxlen(link(x))}+1\)

namespace SAM{
	struct node{
		int len,link,nxt[26];
	}a[N<<1];
	int las=0,tot=0;
	inline void init() {a[0].link=-1;}
	inline void insert(int x) {
		int cur=++tot;cnt[cur]=1;
		a[tot].len=a[las].len+1;
		int p=las;
		while(p!=-1 && !a[p].nxt[x]) {a[p].nxt[x]=cur;p=a[p].link;}
		if(p==-1) a[cur].link=0;
		else {
			int q=a[p].nxt[x];
			if(a[p].len+1==a[q].len) a[cur].link=q;
			else {
				int clone=++tot;
				a[clone]=a[q];
				a[clone].len=a[p].len+1;
				a[q].link=a[cur].link=clone;
				while(p!=-1 && a[p].nxt[x]==q) {a[p].nxt[x]=clone;p=a[p].link;}
			}
		}
		las=cur;
	}
}

(注意空间要开两倍)

LG P3804 【模板】后缀自动机 (SAM)

性质

  • 每个前缀的终止节点不同

  • 每个节点所代表的子串集合一定是一段 “连续” 的字符串,形式化的说集合中的每个子串都是最长子串的一个后缀,并且长度连续

应用

广义 SAM

  • 大概是用来解决多个模式串

  • 我们可以认为每个字符串的每个位置都是独一无二的,也就是在算等价类的时候是不同的

  • 但是为了尽可能的压缩我们的信息,每新插入一个新的字符串的时候,就从头插入

  • 其余的构造和普通的 SAM 是一样的

namespace SAM{
	struct node{
		int link,len,nxt[26];
	}a[N<<1];
	int tot,las=0;
	inline void init() {a[0].link=-1;}
	inline void insert(int x) {
		if(a[las].nxt[x]) {
			int cur=a[las].nxt[x];
			if(a[las].len+1==a[cur].len) return las=cur,void();
			int clone=++tot;
			a[clone]=a[cur];a[clone].len=a[las].len+1;
			a[cur].link=clone;
			int p=las;
			while(p!=-1 && a[p].nxt[x]==cur) a[p].nxt[x]=clone,p=a[p].link;
			return las=clone,void();
		}
		int cur=++tot;
		a[cur].len=a[las].len+1;
		int p=las;
		while(p!=-1 && !a[p].nxt[x]) a[p].nxt[x]=cur,p=a[p].link;
		if(p==-1) a[cur].link=0;
		else {
			int q=a[p].nxt[x];
			if(a[p].len+1==a[q].len) a[cur].link=q;
			else {
				int clone=++tot;
				a[clone]=a[q];
				a[clone].len=a[p].len+1;
				a[q].link=a[cur].link=clone;
				while(p!=-1 && a[p].nxt[x]==q) a[p].nxt[x]=clone,p=a[p].link;
			}
		}
		las=cur;
	}
}using namespace SAM;

LG P6139 【模板】广义后缀自动机(广义 SAM)

性质

  • 如果将所有模式串去重后,每个串的终止节点一定不一样,并且一定代表着所在节点集合的最长子串

  • 一个比较感性的说法(尽量理解因为很重要):对于一个节点所代表的集合的 “范围” 是任意一个它能转移到的状态所代表的集合的 “范围” 的子集,这里的 “范围” 抽象理解,主要是为了证明下面所说的

  • 目前我的观点是 AC 自动机可以做的,广义 SAM /SAM 也可以做

  • 观察 AC 自动机每个节点维护的是 \(fail\) 指针,也就是失配指针,而 SAM 的每个节点的 \(link\) 其实是和失配指针有着差不多的一个意思

  • 但是不能直接按照这么做,这是因为 SAM 的结构还是和 AC 自动机有所不同的

  • 我们转移的时候,对于一个节点是否有贡献一定要注意判断,因为假设当前 \(p\) 转移到 \(a[p].nxt[x]\) ,但是 \(a[p].len!=a[a[p].nxt[x]].len\) ,这说明还有其他的子串可以转移到这个点,但是这个贡献显然是不能算上去的,所以这里要根据每个题目去判断一下

  • 还是需要根据代码理解

LG P5357 【模板】AC 自动机(二次加强版)

参考代码

  • 重点在于理解
	int p=0,fl=1;
	FOR(i,1,len) {
		int x=S[i]-'a';
		while(p!=-1 && !a[p].nxt[x]) p=a[p].link,fl=1;
		if(p==-1) p=0;
		else {
			if(a[p].len+1!=a[a[p].nxt[x]].len)  fl=0;
			p=a[p].nxt[x];
			if(!fl) ++num[a[p].link];
			else ++num[p];
		}
	}

应用

标签:子串,每个,SAM,后缀,len,集合,自动机,节点
来源: https://www.cnblogs.com/kzos/p/16343090.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有