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. 在Deepin 20.2系统中换源并全新图解安装MySQL数据库

    在Deepin 20.2系统中换源并全新图解安装MySQL数据库 https://www.ywnz.com/linuxysjk/9249.html ubuntu下apt-get彻底卸载mysql 删除 ...

  2. ubuntu下安装php pdo扩展和导入数据库

    默认安装的php不存在pdo扩展,因此在使用到的时候会报错,直接使用这个命令 apt-get install php-mysql 就可以成功安装pdo扩展 安装完数据库后需要导入sql语句,先进入数据 ...

  3. tomcat(2)- tomcat目录结果和配置文件

    目录 1 Tomcat目录结构 2 Tomcat的配置文件 2.1 server.xml配置文件 2.2 server.xml配置文件结构 2.3 WEB应用自动部署 2.4 配置文件各个组件的关联 ...

  4. web页面打开直接调用vlc播放视频

    简介 大家都知道现在我们在网页所播放的视频都是h264编码格式,可以供所有设备正常播放.然而,相比h265它的体积更大.质量更差.目前h265大多应用于安防,体积小可以更好的存储,不过它也有着缺点,成 ...

  5. C语言:如何实现在txt文件中删除超链接、统计单词数量、生成单词列表 (文本流操作并解决乱码)

    1.首先读取原文件内容文本流(包含中英文) 2.删除超链接 3.统计单词数量 4.去除重复单词 读取文件需要自己在文本笔记中保存一个网页,保存为txt文件 注意的是,在这个代码实现过程中,我学到的是如 ...

  6. 使用XCA自制CA证书并签发https证书

    序言 本文目的是使公司内网部署的Web可以使用https的方式访问 现有部署的系统有用域名访问,有用IP访问,但都是用http的方式 所以打算在公司内网部署统一的CA证书,并可以自己签发对应的域名和I ...

  7. volatile关键字到底有什么作用

    提示:更多优秀博文请移步博主的GitHub仓库:GitHub学习笔记.Gitee学习笔记 volatile是Java提供的一种轻量级的同步机制.Java 语言包含两种内在的同步机制:同步块(或方法)和 ...

  8. etcd MVCC 存储结构及流程

    什么是 MVCC MVCC 是 Multi-Version Concurrency Control 的缩写,即多版本并发控制.它是一种并发控制的方法,用于在数据库系统中实现事务的隔离性.MVCC 是一 ...

  9. jQuery——链式操作

    链式操作,即连续操作. 例如: <!DOCTYPE html> <html> <head> <script src="https://cdn.boo ...

  10. LeetCode 39. Combination Sum 组合总和 (C++/Java)

    题目: Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), ...