后缀自动机 (SAM) 学习笔记

news/2025/1/15 21:23:13/文章来源:https://www.cnblogs.com/Rock-N-Roll/p/18673731

\(\text{后缀自动机 (SAM) 学习笔记}\)

一、定义

字符串 \(s\) 的 SAM 是一个接受 \(s\) 的所有后缀的最小 DFA (确定性有限自动机或确定性有限状态自动机),也就是说:

  • SAM 是一张有向无环图。它的结点是图中的状态,边是状态之间的转移。
  • SAM 有源点 \(t_0\),且其它各结点均可从 \(t_0\) 出发到达。
  • SAM 中每个转移都标有一个字母,且从一个结点出发的所有转移都是不同的。
  • SAM 存在一些终止状态。特殊地,到达一个终止状态时从 \(t_0\) 到该状态的路径连接起来是字符串 \(s\) 的后缀。反之,\(s\) 的每个后缀同样可从一条由 \(t_0\) 到某个终止状态的路径构成。
  • 满足这样条件的自动机有多个,而 SAM 的结点数是最少的。

让我们举一个例子来描述一个对于字符串 \(\texttt{abcbc}\) 的 SAM:picture1

需要注意的是,SAM 的结点个数和边数都是 \(O(n)\) 的。具体地,一个 SAM 最多会有 \(2\times n-1\) 个结点和 \(3\times n-4\) 条转移边。

二、\(\operatorname{endpos}\) 等价类及其性质

这一部分的内容,似乎和 SAM 没有直接关系,但却是 SAM 中很重要的一部分。我们需要证明关于它的一些性质,并得出一些结论,这是我们构建 SAM 的基础。

1. 定义

对于字符串 \(s\) 的一个子串,它在原串中会出现若干次。一个子串 \(p\)\(s\) 中出现的右端点位置的集合,就称为 \(\operatorname{endpos}(p)\)。对于上文中串 \(\texttt{abcbc}\),对于字符串 \(\texttt{bc}\),其 \(\operatorname{endpos}\) 集合为 \(\{2,4\}\)。需要说明的是,每一个 \(\operatorname{endpos}\) 等价类对应着 SAM 上的一个结点。根据定义我们显然可以这样做,这样 SAM 中一个状态就会对应这个 \(\operatorname{endpos}\) 等价类中所有的字符串。

2. 性质及其证明

  1. 若串 \(s_1,s_2\) 满足 \(\operatorname{endpos}(s_1)=\operatorname{endpos}(s_2)\),且 \(s_1\neq s_2\),则 \(\operatorname{len}(s_1)\neq \operatorname{len}(s_2)\)

证明:若存在 \(s_1,s_2\) 满足 \(\operatorname{endpos}(s_1)=\operatorname{endpos}(s_2)=R,s_1\neq s_2,\operatorname{len}(s_1)=\operatorname{len}(s_2)=l\),则对于任意 \(pos\subset R\),由 \(s_1\neq s_2\)\(s[pos-l+1,pos]\neq s[pos-l+1,pos]\),显然矛盾。

  1. 对于 \(s\) 的任意后缀 \(t_s\),有 \(\operatorname{endpos}(s)\subset \operatorname{endpos}(t_s)\)

证明:由 \(\operatorname{endpos}\) 的定义知道每个 \(s\) 能匹配到的右端点 \(t_s\) 必能匹配。

  1. 若两个不同的串 \(s_1,s_2\) 满足 \(\operatorname{endpos}(s_1)=\operatorname{endpos}(s_2)=R\),则对于 \(\operatorname{len}(s_1)\le l \le \operatorname{len}(s_2)\),一定存在 \(s_3\) 满足 \(\operatorname{len}(s_3)=l\)\(\operatorname{endpos}(s_3)=R\)

证明:由 1,\(\operatorname{len}(s_1)\neq \operatorname{len}(s_2)\),不妨设 \(\operatorname{len}(s_1)<\operatorname{len}(s_2)\)。令 \(s_3=s_2[l_2-l+1,l_2]\),由 2,\(R\subset \operatorname{endpos}(s_3),\operatorname{endpos}(s_3)\subset R\)。因此 \(\operatorname{endpos}(s_3)=R\)

  1. \(\operatorname{endpos}\) 集合相等的字符串的长度必然是连续的。

证明:设其中两个不同串为 \(s_1,s_2\),由 1,可不妨设 \(\operatorname{len}(s_1)<\operatorname{len}(s_2)\)。由 3,必然有 \(\operatorname{len}(s_1)\le l \le \operatorname{len}(s_2)\) 满足 \(\operatorname{endpos}=R\)

  1. 对于两个 \(\operatorname{endpos}\) 集合 \(R_a,R_b\),要么 \(R_a\subseteq R_b\),要么 \(R_a\cap R_b=\varnothing\)

证明:设 \(R_a\cap R_b=r\neq \varnothing\),那么设从 \(t_0\)\(R_a,R_b\) 结点路径表示的字符串的集合为 \(S_a,S_b\)。记 \(\max_s,\) 表示集合 \(S_a\) 中最长的串的长度,\(\min_a\) 同理,则由 4,\([\min_a,\max_a]\cap [\min_b,\max_b]=\varnothing\)。不妨设 \(\max_b<\min_a\),则对于任意 \(s_a\in S_a\),有 \(\operatorname{len}(a)>\operatorname{len}(b)\)。又因为 \(R_a\cap R_b=r\neq \varnothing\),由 2,\(R_a\subset R_b\)。考虑 \(R_a=R_b\) 的情形,则 \(R_a\subseteq R_b\)

三、 parent 树及 SAM 的复杂度

根据上面的性质,任意两个 \(\operatorname{endpos}\) 集合或是不相交,或是其中一个是另一个的子集。那么对于任意一个不为初始状态的状态 \(a\),一定恰好存在一个状态 \(b\) 满足 \(\operatorname{endpos}(a)\subseteq \operatorname{endpos}(b)\)\(\max_b=\max_a-1\)。这种关系可以抽象成一个树形结构,记非根状态 \(x\) 在 parent 树上的父亲为 \(\operatorname{link}(x)\)。那么容易发现的性质是一个结点在 parent 树上的子结点至少有两个,否则其 \(\operatorname{endpos}\) 集合应当相同。且子结点代表的 \(\operatorname{endpos}\) 集合互不相交。那么仍然以串 \(\texttt{abcbc}\) 举例,我们可以用绿色的线表示 parent 树上的边。

image

让我们进一步发现 parent 树上的一些奇妙性质:

  1. parent 树有 \(\operatorname{len}(s)\) 个儿子。

证明:显然会存在的叶子结点 \(\operatorname{endpos}\) 集合为 \(\{1\},\{2\},\cdots,\{\operatorname{len}(s)\}\)

  1. parent 树的状态不会超过 \(O(n)\) 级别。

证明:由于一个点至少有两个子结点,那么新增一个结点必然会删去两个结点,因此最多新增 \(n-1\) 个非叶子结点。于是总的状态级别是 \(O(n)\)

那么我们已经证明了 SAM 状态数是 \(O(n)\) 的。需要知道的是 SAM 的转移边数同样是 \(O(n)\) 的,不过这个性质没有状态数那么重要,且较难证明,因此略去。

四、SAM 的构造

1. 构造流程

初始情况是只有状态 \(t_0=1\),其 \(\operatorname{len}=0,\operatorname{link}=0\),现在将字符 \(c\) 加入 SAM 中,加入之前 SAM 的最终状态为 \(p\)

我们创建新的状态 \(np\),令 \(\operatorname{len}(np)=\operatorname{len}(p)+1\)。从 \(p\) 开始跳 \(\operatorname{link}\),若没有 \(c\) 的转移,添加到 \(np\) 为字符 \(c\) 的转移,直到找到一个有该转移的状态,改这个状态为 \(p\)。若没有找到 \(p\),那么其 \(\operatorname{endpos}\) 是一个全新的 \(\operatorname{endpos}\),直接令 \(\operatorname{link}(np)=1\) 即可,否则我们记 \(p\) 关于 \(c\) 的转移为 \(q\)

\(\operatorname{len}(q)=\operatorname{len}(p)+1\),那么显然令 \(\operatorname{link}(np)=q\) 是正确的。考虑 \(\operatorname{len}(q)\neq \operatorname{len}(p)+1\) 的情形:我们将状态 \(q\) 复制至 \(nq\),但将 \(\operatorname{len}(nq)\) 置为 \(\operatorname{len}(p)+1\),并将 \(q,np\)\(\operatorname{link}\) 信息指向 \(nq\)

最后,我们从 \(p\) 开始跳 \(\operatorname{link}\),若 \(p\)\(c\) 的转移且转移到了 \(q\),将这个转移改到 \(nq\) 即可,直到找不到或是回到源点停止。

需要知道的是,建完这个 SAM 后对应的终止状态就是 \(np\)

2. 正确性证明

需要证明的部分只有 \(\operatorname{len}(q)\neq \operatorname{len}(p)+1\) 的部分。此时显然 \(\operatorname{len}(q)>\operatorname{len}(p)+1\),也就是状态 \(q\) 在对应长度为 \(\operatorname{len}(p)+1\) 后缀的同时也对应了更长的子串。于是将状态 \(q\) 拆一个状态 \(nq\) 出来,且将其 \(\operatorname{len}\) 设为 \(\operatorname{len}(p)+1\)。这样一来,\(nq\) 继承 \(q\) 的其它信息是理所当然的。同时需要留意的是,要将状态 \(p\) 原有到 \(q\) 的转移改到 \(nq\),于是跳 \(\operatorname{link}\) 的后缀直到找不到转移 \((c,q)\) 为止。

需要知晓的是,这样构建 SAM 的时间复杂度是 \(O(n)\)。这是建立在字符集大小为常数的前提下。否则一般使用 std::map 来存边,此时时间复杂度为 \(O(n\log |\sum|)\) 而空间复杂度为 \(O(n)\)

这里给出 SAM 的一般实现:

struct SAM {int len, fa;int s[M];
} sam[N];
int tot = 1, lst = 1;
void insert(int c) {int p = lst, np = lst = ++tot;sam[np].len = sam[p].len + 1;for (; p && !sam[p].s[c]; p = sam[p].fa) sam[p].s[c] = np;if (!p) sam[np].fa = 1;else {int q = sam[p].s[c];if (sam[q].len == sam[p].len + 1) sam[np].fa = q;else {int nq = ++tot;sam[nq] = sam[q], sam[nq].len = sam[p].len + 1;sam[q].fa = sam[np].fa = nq;for (; p && sam[p].s[c] == q; p = sam[p].fa) sam[p].s[c] = nq;}}
}

五、SAM 的基础应用

1. 求本质不同子串个数

一般的方法是求每个状态内子串的个数。也就是 \(\sum \operatorname{len}(i)-\operatorname{link}(\operatorname{len}(i))\)

2. 求第 \(k\) 小子串

考虑到每个子串唯一对应着 SAM 上一条路径,于是转化为求 SAM 上字典序第 \(k\) 小的路径。于是简单 dp 可以处理。

3.求两个字符串的最长公共子串

对于两个字符串 \(s,t\),对于 \(s\) 建出后缀自动机,对 \(t\) 进行匹配处理。我们使用两个变量进行匹配:当前状态 \(p\) 和当前长度 \(l\)。初始时 \(p=t_0,l=0\)

\(p\) 存在字符 \(t_i\) 的转移时,我们转移长度并让 \(l\) 加一即可。若不存在 \(p\) 的转移,需要将 \(p\)\(\operatorname{link}\) 数组知道满足当前字符的转移。对于时间复杂度,显然每次最多使 \(l\) 加一,或是将 \(l\) 减小一些,调整加减顺序不难得到总的时间复杂度为 \(O(|s|+|t|)\)

给出代码实现:

int fnd(char *s) {int p = 1, ans = 0, l = strlen(s), res = 0;for (int i = 0; i < l; i++) {int c = s[i] - '0';while (p > 1 && !sam[p].s[c]) {p = sam[p].fa;ans = sam[p].len;}if (sam[p].s[c]) {p = sam[p].s[c];++ans;}res = max(res, ans);}return res;
}

4. 线段树合并维护 \(\operatorname{endpos}\) 集合

考虑在 parent 树上对 \(\operatorname{endpos}\) 集合进行线段树合并,这样通常可以维护出每个点 \(\operatorname{endpos}\) 的一些信息,在一些题目中会用到。

5. SAM 求后缀的最长公共前缀

考虑将字符串反向后插入 SAM 中,这样问题转化为了前缀的最长公共后缀,那么 parent 树上所有父亲串一定是儿子串的前缀。于是找到两个字符串对应的结点,求它们的 LCA 即可。

六、广义 SAM

广义 SAM,就是对多个字符串建出的 SAM。如果暴力将它们连接,往往会出现各种各样的问题,因此需要掌握建立正确的广义 SAM 的方法。

我们先针对这些字符串建出 Trie,在此基础上将 Trie 的每条边建到广义 SAM 中即可。我们通常适用的方法是离线 BFS。下面给出代码实现:

struct Trie {int fa, ch;int s[M];
} tr[N];
int cnt = 1;
void ins(char *s) {int l = strlen(s), p = 1;for (int i = 0; i < l; i++) {int ch = s[i] - '0';if (!tr[p].s[ch]) {tr[p].s[ch] = ++cnt;tr[cnt].fa = p;tr[cnt].ch = ch;}p = tr[p].s[ch];}
}
struct SAM {int len, fa;int s[M];
} sam[N];
int tot = 1;
int insert(int c, int lst) {int p = lst, np = lst = ++tot;sam[np].len = sam[p].len + 1;for (; p && !sam[p].s[c]; p = sam[p].fa) sam[p].s[c] = np;if (!p) sam[np].fa = 1;else {int q = sam[p].s[c];if (sam[q].len == sam[p].len + 1) sam[np].fa = q;else {int nq = ++tot;sam[nq] = sam[q], sam[nq].len = sam[p].len + 1;sam[q].fa = sam[np].fa = nq;for (; p && sam[p].s[c] == q; p = sam[p].fa) sam[p].s[c] = nq;}}return lst;
}queue<int>q;
int pos[N];
void build() {for (int i = 0; i < M; i++)if (tr[1].s[i]) q.push(tr[1].s[i]);pos[1] = 1;while (!q.empty()) {int p = q.front();q.pop();pos[p] = insert(tr[p].ch, pos[tr[p].fa]);for (int i = 0; i < M; i++)if (tr[p].s[i]) q.push(tr[p].s[i]);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/869786.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS-39 Math 对象

Math是JavaScript的原生对象,提供各种数学功能。 Math.abs() 1、Math.abs方法返回参数值的绝对值 Math.abs(1)//1 Math.abs(-1)//1 2、Math.max(),Math.min() Math.max方法返回参数之中最大的那个值,Math.min返回最小的那个值。如果参数为空,Math.min返回Infinity,Math.max返…

单独变量操作数据库使用@Param

@Param// 单独变量传递过去的时候需要和数据库字段进行对照,哪怕命名相同也需要@Param@Update("update admingoin set admingoinpict = #{admingoinpict} where admingoinuid = #{admingoinuid}")void aaaaa(@Param("admingoinuid")String admingoinui…

2025.1.14——1200

2025.1.14——1200Q1. 1200 You have \(n\) sticks, numbered from \(1\) to \(n\). The length of the \(i\)-th stick is \(2^{a_i}\). You want to choose exactly \(3\) sticks out of the given \(n\) sticks, and form a non-degenerate triangle out of them, using the…

【前端】前端需要知道的缓存知识总结

引言📇 HTTP缓存是一种用于提高网站性能和减少带宽使用的技术。当用户访问一个网页时,浏览器会下载页面上的所有资源(如HTML、CSS、JavaScript等),这些资源会占用大量的带宽和时间。为了减少这些资源的加载时间,HTTP缓存机制被引入。 缓存分为强缓存和协商缓存两种,强缓…

[CF2057G] Secret Message 题解

神秘题目。 题目的条件十分神奇,\(|A| \le \frac{1}{5} (s+p)\),不知所云。 一开始尝试用皮克定理转化,但是 failed。 阅读理解之后发现有一个(很典)的套路,就是构造出五组方案,使得 \(\sum_{cyc} |A| = s+p\),这样就一定有一组方案,面积小于等于 $ \frac{1}{5} (s+p)…

装机重启后无法进入图形界面

装机重启后无法进入图形界面 A problem has occurred and the system cant recover. Please log out and try again. 主要原因可能是安装的软件包未更新,更新即可 (yum update) 报错截图首先进入命令行界面并登录root账户 Ctrl+Alt+F2联网 对于rocky系统,查看网络设备: nmcl…

【MATLAB】自学记录之基于某楼栋房价数据绘制三维网格图

1. 前言 基于某小区某一楼栋各个户型及楼层之间对应的出售价格表,通过MATLAB脚本进行读取解析,并绘制成三维网格图,从而能够直观地以可视化的角度观察户型位置(东边户、西边户、中间连廊户)、楼层位置(高中低楼层)等因素是否与出售价格存在一定的影响关系。2. 预置条件序…

【前端】谈谈水印实现的几种方式

遇到问题 日常工作中,经常会遇到很多敏感的数据,为防止数据的泄露,我们要在数据上做一些”包装“。目的就是让那些有心泄露数据的”不法分子“迫于严重的”舆论压力“而放弃不法行为,使之”犯罪未遂“,达到不战而屈人之兵的效果。而在安全部门工作的我们,数据安全的观念早…

插头DP记录

关于插头dp。AAA黑题批发。 这个东西好像设问还挺广泛的,做到哪写到哪吧。 得先了解一下轮廓线dp定义。 概念 设问广泛但是总体来说是连通性相关状压dp的一类设计方法。 骨牌覆盖问题 比如说,最简单的,问你 \(n*m\) 的棋盘格里能放多少 \(1*2\) 的骨牌。 考虑把一个节点分为…

03_LaTeX之文档元素

在知道了如何输入文字后,在本章了解一个结构化的文档所依赖的各种元素——章节、目录、列表、图表、交叉引用、脚注等等。目录03_\(\LaTeX{}\) 之文档元素章节和目录章节标题目录文档结构的划分标题页交叉引用脚注和边注特殊环境列表对齐环境引用环境摘要环境代码环境表格列格…

THREE.js学习笔记6——Geometries

这一小节学习THREE.js中的物理模型。 什么是geometry?(英文解释,翻译为中文就看不懂了,直接看英语吧)Composed of vertices (point coordinates in 3D spaces)and faces (triangles that join those vertices to create a surface) Can be used for meshes but also for par…

第三节 回归实战

数据处理超参:人为指定不能改变测试数据只有x没有标签y 训练数据拆分,82开,作训练集和验证集(验证模型好坏),模型训练不是一路上升的过程,训练几次验证一次,最好的模型save下来 one-hot独热编码 猪(1 0 0) 狗(0 1 0) 猫(0 0 1) def get_feature_importance(feature_data, label…