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. HTTPS 是如何进行安全传输的 ?

    概述 现代密码学对信息的处理主要离不开以下的三种形式: 摘要:主要用于数据校验,例如存储密码等,摘要是对信息进行单向的哈希,改变信息的原有形态,因为哈希函数的特点是易变性(即使微小的变化也会产生完全不 ...

  2. 对于Docker和Podman的一点使用经验

    前言:本文会以多个实际的线上例子,分享自己对于Docker和Podman的一点使用经验及踩过的坑,希望对读者有一点帮助. 本文bash脚本初步加工后可直接使用(兼容mac和linux系统),对于关键点 ...

  3. node.js环境在Window和Mac中配置,以及安装cnpm和配置Less环境

    Node.js 和cnpm安装 最近准备学习vue.js,但首先需要配置电脑的环境.配置node.js. 1.在node(https://nodejs.org/en/)官网上下载安装node.js,两 ...

  4. 前端使用 Konva 实现可视化设计器(11)- 对齐效果

    这一章补充一个效果,在多选的情况下,对目标进行对齐.基于多选整体区域对齐的基础上,还支持基于其中一个节点进行对齐. 请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,欢迎来提 I ...

  5. 利用英特尔 Gaudi 2 和至强 CPU 构建经济高效的企业级 RAG 应用

    检索增强生成 (Retrieval Augmented Generation,RAG) 可将存储在外部数据库中的新鲜领域知识纳入大语言模型以增强其文本生成能力.其提供了一种将公司数据与训练期间语言模型 ...

  6. Leetcode数组-二分法

    Leetcode数组-二分法 二分法学习地址 二分法 704. 二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 targe ...

  7. MySQL学习笔记-数据控制语言

    SQL-数据控制语言(DCL) DCL语句用于管理数据库用户,控制数据库的访问权限 一. 管理用户 1. 查询用户 # 访问mysql数据库 use mysql; #查询user表 select * ...

  8. 机器学习笔记(2): Logistic 回归

    Logistic 回归是线性回归中一个很重要的部分. Logistic 函数: \[\sigma(x) = \frac {L} {1 + \exp(-k(x - x_0))} \] 其中: \(L\) ...

  9. Prometheus 监控平台组件深度讲解

    Prometheus 的重要性和流行度已经无需多言.直入主题,本文对 Prometheus 监控平台的各个组件做深度讲解,希望能帮助读者更好地理解 Prometheus. 监控系统的核心逻辑 对于一套 ...

  10. Woothosting 6$/年 vps测评

    当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解 Woothosting 6$/年 vps测评** 日期:2018-7- ...