SAM

定理

SAM 由 parent 树与一张 DAG 构成,他们共用点集。

\(endpos(s)\) 表示 \(s\) 出现的所有位置上最后一个字符所处位置的集合。

SAM 中 DAG 上每条路径对应原串上的一个子串,一个子串也与其对应。

在 SAM 的 DAG 上到达一个点的所有子串的 endpos 相同。

一个节点上储存的最长的串为 \(len_i\)。

在 parent 树上点 \(u\) 的父亲节点是与 \(u\) endpos 不相同的 \(len_{u}\) 的最长真后缀。

不难发现点 \(u\) 的 endpos 包含所有满足 \(v \in son_{u}\) 的 \(v\) 的 endpos,不难发现一个点 \(u\) 存储的串就是所有长度小于等于自己并大于其父节点的所有后缀。

构建

增量法

已知串 \(S\) 的 SAM,如何构造 \(S+c\) 的 SAM?

找到一个节点 \(u\) 使得 \(len_{u} = |S|\)。

考虑在 parent 树上 \(u\) 的所有祖先包含 \(S\) 的所有真后缀。

在这条路径上显然应当是满足所有深度大于 \(t\) 的点没有 \(c\) 这条出边。

新建一个点 \(p\) 使得其 \(len_{p} = len_{u} + 1\) 等于说是包括了 \(c\)。

让所有没有 \(c\) 这条出边的点向 \(p\) 连一条 \(c\) 的边。

到此为止 DAG 的维护就完成了。

接下来考虑维护 parent 树与 endpos。

大力分讨。

  1. 假设所有点都向 \(p\) 连边,就说明 \(c\) 根本就没出现过,那么其所有后缀的 endpos 都是 \(len_{p}\) 那么将 \(p\) 在 parent 树上的父亲改为根即可。

  2. 假设存在点向 \(p\) 连边,找到第一个存在向 \(c\) 的边的点 \(x\),假设其向 \(c\) 的边指向 \(q\),假若 \(len_{x} + 1 = len_{q}\),直接令 \(fa_{p} = q\)。因为 \(p\) 最长只能跳这来。

  3. 假若 \(len_{x} + 1 \not = len_{q}\) 那就有点麻烦,那么此时需要分裂 \(q\),假设你分裂出了新点 \(nq\),直接令 \(SAM_{nq} = SAM_{q}\),然后手动将 \(SAM_{nq,len} = SAM_{x,len} + 1\),然后让 \(SAM_{q,fa} = nq\),然后让 \(SAM_{p,fa} = nq\)。但是此时 DAG 的性质又似了,我们需要把所有 \(x\) 的祖先当中原本指向 \(q\) 的点全部指向 \(nq\)。

至此你发现 parent 树和 endpos 全部维护好了。

复杂度是神奇的线性乘字符集。

不难发现一个点的最长串一定都这个点上不会被分类,那么每次插入字符新建的点一定代表一个前缀,跳 parent 树即可跳出子串。

广义 SAM

其实差不多。

有点小区别,当你求所有串的公共子串时,需要把每个子串的公共子串都遍历一遍,自己遍历过的点自己不再遍历但是其他点仍需要遍历,复杂度是 \(O(len \sqrt len)\) 的。

考虑一个串的遍历复杂度是 \(O(len^2,\sum |S|)\) 那么均摊下来一个字符的复杂度是 \(O(\min(len,\frac{\sum |S|}{len}) \leq O(\sqrt{\sum |S|})\),所以总复杂度是 \(O(\sum |S| \sqrt{\sum |S|})\)。

SA

后缀数组 \(rk_{i}\) 表示字典序第 \(i\) 的后缀为 \([rk_{i},n]\)。

考虑怎么构造。

有一个无脑的 \(O(n \log^2 n)\) 做法,不提了。

考虑倍增。

考虑把每个 \([i,i+2^k]\) 拿出来排序,因为上一次已经排好了,所以用两个关键字做基数排序即可。

height 数组

令 \(lcp(i,j)\) 表示 \([rk_i,n]\) 与 \([rk_j,n]\) 的最长公共前缀,有 \(lca(i,j) = \min(lca(i,j),lca(j,k))\),假若我们处理出 \(height_{i} = lcp(i,i-1)\) 就能把 \(lca(i,j)\) 变成 RMQ 问题。

考虑求出 \(height\) 数组。这里给一个新数组 \(h\) 数组。

考虑 \(height_{i} = h_{rk{i}}\)。

有 \(h_{i+1} \geq h_{i} - 1\) 然后考虑去暴力扩展。

大不了全部扩展完,是 \(O(n)\) 的。

板子

广义 SAM

struct SAM{
int len,fa;
int son[26];
}nd[maxn];
int tot,lst;
void ins(char c){
if(nd[lst].son[c-'a']!=0){
int q=nd[lst].son[c-'a'],v=lst;
if(nd[q].len==nd[v].len+1){
lst=q;
return ;
}
int nq=++tot;
lst=nq;
nd[nq]=nd[q];
nd[nq].len=nd[v].len+1;
nd[q].fa=nq;
while(v!=0&&nd[v].son[c-'a']==q) nd[v].son[c-'a']=nq,v=nd[v].fa;
return ;
}
int u=++tot,v=lst;
nd[u].len=nd[lst].len+1;
lst=u;
while(v!=0&&nd[v].son[c-'a']==0) nd[v].son[c-'a']=u,v=nd[v].fa;
if(v==0){
nd[u].fa=1;
return ;
}else{
int q=nd[v].son[c-'a'];
if(nd[v].len+1==nd[q].len){
nd[u].fa=q;
return ;
}else{
int nq=++tot;
nd[nq]=nd[q];
nd[nq].len=nd[v].len+1;
nd[u].fa=nq;
nd[q].fa=nq;
while(v!=0&&nd[v].son[c-'a']==q) nd[v].son[c-'a']=nq,v=nd[v].fa;
return ;
}
}
}
int rt;
void insert(string s){
lst=rt;
for(char x:s) ins(x);
}

SAM & 广义 SAM & SA 学习笔记的更多相关文章

  1. loj#6031. 「雅礼集训 2017 Day1」字符串(SAM 广义SAM 数据分治)

    题意 链接 Sol \(10^5\)次询问每次询问\(10^5\)个区间..这种题第一感觉就是根号/数据分治的模型. \(K\)是个定值这个很关键. 考虑\(K\)比较小的情况,可以直接暴力建SAM, ...

  2. 后缀数组SA学习笔记

    什么是后缀数组 后缀数组\(sa[i]\)表示字符串中字典序排名为\(i\)的后缀位置 \(rk[i]\)表示字符串中第\(i\)个后缀的字典序排名 举个例子: ababa a b a b a rk: ...

  3. SA 学习笔记

    后缀数组是解决字符串问题的有力工具--罗穗骞 后缀数组是对字符串的后缀排序的一个工具, sa将排名为i的字符串的开头位置记录下来, rnk将开头位置为i的字符串的排名记录下来. https://www ...

  4. SAM学习笔记

    SAM学习笔记 后缀自动机(模板)NSUBSTR(Caioj1471 || SPOJ 8222) [题意] 给出一个字符串S(S<=250000),令F(x)表示S的所有长度为x的子串中,出现次 ...

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

    最近学了SAM已经SAM的比较简单的应用,SAM确实不好理解呀,记录一下. 这里提一下后缀自动机比较重要的性质: 1,SAM的点数和边数都是O(n)级别的,但是空间开两倍. 2,SAM每个结点代表一个 ...

  6. Luogu P3181 [HAOI2016]找相同字符 广义$SAM$

    题目链接 \(Click\) \(Here\) 设一个串\(s\)在\(A\)中出现\(cnt[s][1]\)次,在\(B\)中出现\(cnt[s][2]\)次,我们要求的就是: \[\sum cnt ...

  7. 【BZOJ 3473】 字符串 (后缀数组+RMQ+二分 | 广义SAM)

    3473: 字符串 Description 给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串? Input 第一行两个整数n,k. 接下来n行每行一个字符串 ...

  8. 4.2 省选模拟赛 旅行路线 广义SAM

    \(n\leq 100000\) 题目上求出 多少条本质不同的路线. 首先定义了 相似的城市为度数相同的城市. 还定义了两条路线相同当且仅当长度相同 且对应位置的城市都是相似的. 考虑这张图的形态 n ...

  9. CF204E-Little Elephant and Strings【广义SAM,线段树合并】

    正题 题目链接:https://www.luogu.com.cn/problem/CF204E 题目大意 \(n\)个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少\(k\ ...

  10. P6793-[SNOI2020]字符串【广义SAM,贪心】

    正题 题目链接:https://www.luogu.com.cn/problem/P6793 题目大意 给出两个长度为\(n\)的字符串,取出他们所有长度为\(k\)的连续子串分别构成两个可重集合\( ...

随机推荐

  1. Sermant在异地多活场景下的实践

    本文分享自华为云社区<Sermant在异地多活场景下的实践>,作者:华为云开源. Sermant社区在1.3.0和1.4.0版本相继推出了消息队列禁止消费插件和数据库禁写插件,分别用于解决 ...

  2. WebKit Inside: CSS 样式表的解析

    CSS 全称为层叠样式表(Cascading Style Sheet),用来定义 HTML 文件最终显示的外观. 为了理解 CSS 的加载与解析,需要对 CSS 样式表的组成,尤其是 CSS Sele ...

  3. 记一次线上Redis内存占用过高、大Key问题的排查

    问题背景 在一个风和日丽的下午,公司某项目现场运维同学反馈,生产环境3个Redis的Sentinel集群节点内存占用都很高,达到了17GB的内存占用量. 稍加思索,应该是某些Key的Value数据体量 ...

  4. Js中valueOf和toString区别和使用

    对于number.string.Boolean.object.symbol数据类型调用valueOf方法,得到的都是数据本身(null.undefined两种类型上的原型链上没有valueOf方法) ...

  5. Vue3:Cannot read properties of null (reading 'isCE')

    Cannot read properties of null (reading 'isCE')   这个问题是在vue3中引入elementui的列表框时出现的.经过网上查询,有说是装了两个vue版本 ...

  6. MySQL的索引优化

    一.索引的使用场景 1.全值匹配 通过主键索引查询 mysql> explain select * from t_goods where id = 1 \G; ***************** ...

  7. ​一款开源的.NET程序集反编译、编辑和调试神器

    前言 说到.NET相关的反编译工具大家脑海里第一个想到的工具是什么?ILSpy.dnSpy.还是dotPeek?咱们今天的主要内容是讲讲dnSpyEx(dnSpyEx是dnSpy项目的非官方Fork维 ...

  8. sass @extend(继承)指令详解

    在设计网页的时候常常遇到这种情况:一个元素使用的样式与另一个元素完全相同,但又添加了额外的样式. 通常会在 HTML 中给元素定义两个 class,一个通用样式,一个特殊样式. 普通CSS的实现 接下 ...

  9. 大数据之Hadoop集群的HDFS压力测试

    测试HDFS写性能 原文:sw-code 1)写测试的原理 2)测试内容:向HDFS集群写10个128MB的文件(3个机器每个4核,2 * 4 = 8 < 10 < 3 * 4 =12) ...

  10. 2024-05-22:用go语言,你有一个包含 n 个整数的数组 nums。 每个数组的代价是指该数组中的第一个元素的值。 你的目标是将这个数组划分为三个连续且互不重叠的子数组。 然后,计算这三个子数

    2024-05-22:用go语言,你有一个包含 n 个整数的数组 nums. 每个数组的代价是指该数组中的第一个元素的值. 你的目标是将这个数组划分为三个连续且互不重叠的子数组. 然后,计算这三个子数 ...