Part. 1 基本信息

Part. 1-1 SAM 的构成。

SAM 由两个东西构成,一个是一个 DAWG,还有一棵外向树,叫 parent tree。

比如,给你一个字符串 \(S=\sf abbabb\),它的 SAM 长成这样:

SAM 的 DAWG 大概可以理解为把字符串的所有后缀插入一个 Trie。当然如果你暴力插,点数为 \(\mathcal{O}(n^2)\)。

不过显然我们可以把一些重复的结点 rua 在一起,点数差不多就成了 \(\mathcal{O}(n)\),还要带个 \(2\) 的常数。

然后,\(S\) 的子串都可以被 SAM 的 DAWG 上的某条路径表示,很显,对吧。

DAWG 的边就是上图中的黑边,蓝边就是 parent tree 的树边。

Part. 1-2 符号约定

我们称 \(S[l,r]\) 为字符串 \(S\) 的 \([l,r]\) 的子串,相信大家都懂,下标从 \(1\) 开始。

我们称一个集合 \(\text{endpos}(S[l,r])\) 为:对于字符串 \(S\),\(S[l,r]\) 在 \(S\) 中出现的区间为 \([l_{1},r_{1}],\cdots,[l_{k},r_{k}]\),\(\text{endpos}(S[l,r])=\{r_{1},\cdots,r_{k}\}\)。

对于两个子串 \(x,y\),如果 \(\text{endpos}(x)=\text{endpos}(y)\),则称 \(x,y\) 在同一个 \(\text{endpos}\) 等价类中。

显然在 DAWG 上,从根节点到一个结点 \(u\) 能组成的字符串的长度是不同的(不同的路径组成的字符串长度不一定等),我们称从根节点到一个结点 \(u\) 能组成的最长的一个字符串的长度为 \(\text{maxlen}(u)\),最短的称为 \(\text{minlen}(u)\)。

Part. 2 需要知道的

Part. 2-1 \(\text{enspos}\) 的性质

引理 1:对于两个 \(S\) 的非空子串 \(x,y\)(不妨设 \(|x|<|y|\)),若 \(\text{endpos}(x)=\text{endpos}(y)\),则 \(x\) 为 \(y\) 的一个真后缀。

Obviously。

引理 2:对于两个 \(S\) 的非空子串 \(x,y\)(不妨设 \(|x|\le|y|\)),则

\[\begin{cases}
\text{endpos}(x)\subseteq\text{endpos}(y),x\text{ is a suffix of } y, \\
\displaystyle\\
\text{endpos}(x)\cap\text{endpos}(y),\text{otherwise}
\end{cases}
\]

Obviously。

引理 3:在一个 \(\text{endpos}\) 等价类中,将类中的所有子串按长度非递增的顺序排序。每个子串都不会比它前一个子串长,与此同时每个子串也是它前一个子串的后缀。换句话说,对于同一等价类的任一两子串,较短者为较长者的后缀,且该等价类中的子串长度恰好覆盖整个区间 \([x,y]\)。

由引理 1,可知这些子串不会等长(对于两个串,较短串为较长串的真后缀),后面 obviously。

说得简单一点,把一个等价类里面最长的那个字符串拿出来,其他所有串都是该串的 suffix。

Part. 2-2 后缀链接 Link

后缀链接是在原串的 DAWG 上的点连出的边。后缀链接的链接遵循某种规则,且最后构成的是一棵树,我们把后缀链接连出来的树称为 Parent Tree,在后文我们将讲解这种规则。

我们先来看看一个串 \(S=\sf aababa\) 的 Parent Tree 长成副什么样子:

图是从 command_block 那里拿来的,可以沟通删除。(已经修正了原图的勘误)

为了说明方便,我们以一个任意的等价类来说明,我们称这个等价类中长度最大的串为 \(S_{\max}\),同理有 \(S_{\min}\)。

考虑在 \(S_{\max}\) 前面加上一个字符,称为新串为 \(S_{\text{new}}\),显然 \(S_{\text{new}}\) 一定不和 \(S_{\max}\) 在同一等价类里。

我们把上述 加字符 的操作看为分裂出儿子。有了这些,我们可以得出一些性质:

  1. 设 Parent Tree 上的父亲为 \(f\),儿子为 \(u\),有 \(\text{minlen}(u)=\text{maxlen}(f)+1\),显然。

  2. 点数边数皆为 \(\mathcal{O}(n)\),不考虑证明,背着。

  3. 在 Parent Tree 上,一个结点的父亲一定是该结点的后缀,显然。

最后板子自己理解性背住吧,构造方法不想写了。

struct SuffixAutomaton
{
int ID(char c)
{
return c-'a';
}
struct node
{
int len,link,ch[26];
}nodes[3000010];
int n,cntot,las,siz[3000010];
char s[1000010];
vector<int> e[3000010];
void init(int len,char c[])
{
n=len;
for(int i=1;i<=n;++i) s[i]=c[i];
nodes[0].len=las=cntot=0;
nodes[0].link=-1;
}
void extend(char c)
{
int cur=++cntot,one=las,ano=0;
nodes[cur].len=nodes[las].len+1;
while(~one&&!nodes[one].ch[ID(c)])
{
nodes[one].ch[ID(c)]=cur;
one=nodes[one].link;
}
if(one==-1) nodes[cur].link=0;
else
{
ano=nodes[one].ch[ID(c)];
if(nodes[one].len+1==nodes[ano].len) nodes[cur].link=ano;
else
{
int clone=++cntot;
nodes[clone].len=nodes[one].len+1;
nodes[clone].link=nodes[ano].link;
memcpy(nodes[clone].ch,nodes[ano].ch,sizeof(int)*26);
while(~one&&nodes[one].ch[ID(c)]==ano)
{
nodes[one].ch[ID(c)]=clone;
one=nodes[one].link;
}
nodes[ano].link=nodes[cur].link=clone;
}
}
siz[las=cur]=1;
}
void pre()
{
for(int i=1;i<=n;++i) extend(s[i]);
for(int i=1;i<=cntot;++i) e[nodes[i].link].push_back(i);
}
void dfs(int x)
{
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i];
dfs(y);
siz[x]+=siz[y];
}
if(siz[x]^1) ans=max(ans,siz[x]*nodes[x].len);
}
}SAM;

代码中的 siz 是 \(\text{endpos}\) 集合大小。

Note -「Suffix Automaton」SAM的更多相关文章

  1. Note -「Lagrange 插值」学习笔记

    目录 问题引入 思考 Lagrange 插值法 插值过程 代码实现 实际应用 「洛谷 P4781」「模板」拉格朗日插值 「洛谷 P4463」calc 题意简述 数据规模 Solution Step 1 ...

  2. Note -「动态 DP」学习笔记

    目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...

  3. Note -「单位根反演」学习笔记

    \(\mathcal{Preface}\)   单位根反演,顾名思义就是用单位根变换一类式子的形式.有关单位根的基本概念可见我的这篇博客. \(\mathcal{Formula}\)   单位根反演的 ...

  4. Note -「Mobius 反演」光速入门

    目录 Preface 数论函数 积性函数 Dirichlet 卷积 Dirichlet 卷积中的特殊函数 Mobius 函数 & Mobius 反演 Mobius 函数 Mobius 反演 基 ...

  5. Note -「Min_25 筛」“你就说这素因子你要不要吧?你要不要?”

      赛上想写,Track Lost 了属于是. \(\mathscr{Intro}\)   Min_25 筛是用于求积性函数前缀和,同时顺带求出一些"有意思"的信息的筛法.   一 ...

  6. Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门

      进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...

  7. Note -「圆方树」学习笔记

    目录 圆方树的定义 圆方树的构造 实现 细节 圆方树的运用 「BZOJ 3331」压力 「洛谷 P4320」道路相遇 「APIO 2018」「洛谷 P4630」铁人两项 「CF 487E」Touris ...

  8. Note -「Dijkstra 求解 MCMF」

    食用前请先了解 SPFA + Dinic/EK 求解 MCMF. Sol. 总所周知,SPFA 牺牲了.于是我们寻求一些更稳定的算法求解 MCMF. 网络流算法的时间属于玄学,暂且判定为混乱中的稳定. ...

  9. 「TJOI / HEOI2016」字符串

    「TJOI / HEOI2016」字符串 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一个长为 \(n\) 的字符串 \(s\),和 ...

  10. [转帖]「日常小记」linux中强大且常用命令:find、grep

    「日常小记」linux中强大且常用命令:find.grep https://zhuanlan.zhihu.com/p/74379265 在linux下面工作,有些命令能够大大提高效率.本文就向大家介绍 ...

随机推荐

  1. tryhackme-OWASP

    tryhackme-OWASP Top 10部分记录 敏感信息泄露 在assets目录中 可以看到到一个sqlite数据库的webapp.db文件 使用sqlite3 webapp.db .table ...

  2. 推荐一个 C#写的 支持OCR的免费通用扫描仪软件

    NAPS2是一个开源免费软件,体积只有6M不到,支持运行在 Windows, Mac 和 Linux操作系统中,默认就带有简体中文界面,官方默认就提供绿色版,所以解压即可使用,直接可以从官方网站下载: ...

  3. python接口自动化封装导出excel方法和读写excel数据

    一.首先需要思考,我们在页面导出excel,用python导出如何写入文件的 封装前需要确认python导出excel接口返回的是一个什么样的数据类型 如下:我们先看下不对返回结果做处理,直接接收数据 ...

  4. 一文了解 io.Copy 函数

    1. 引言 io.Copy 函数是一个非常好用的函数,能够非常方便得将数据进行拷贝.本文我们将从io.Copy 函数的基本定义出发,讲述其基本使用和实现原理,以及一些注意事项,基于此完成对io.Cop ...

  5. 【调制解调】AM 调幅

    说明 学习数字信号处理算法时整理的学习笔记.同系列文章目录可见 <DSP 学习之路>目录.本篇介绍 AM 调幅信号的调制与解调,内附全套 MATLAB 代码. 目录 说明 1. AM 调制 ...

  6. Git插件报错,Appears to be a git repo or submodule

    Hexo博客需要引入第三方插件,不少包作者误把包项目得.git文件上传到github,或者在插件的github路径下直接下载插件文件夹,结果是插件内含有.git文件,导致下载别的npm包时报错npm ...

  7. java反序列化与反序列化

    java反序列化漏洞 JAVA反序列化漏洞是由于开发者重写了readObject方法,该readObject方法方法调用了别的方法,最终执行到了例如Transfrom方法的危险方法 java序列化过程 ...

  8. 【WebGL系列-03】获取shader变量地址及赋值

    获取shader变量地址及赋值 上一节创建了WebGL程序对象,创建好program对象后,对象中包含顶点着色器和片元着色器,着色器中含有变量,我们需要对其进行赋值后才能够进行绘制. 着色器代码如下: ...

  9. 微服务性能分析工具 Pyroscope 初体验

    Go 自带接口性能分析工具 pprof,较为常用的有以下 4 种分析: CPU Profiling: CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主 ...

  10. Mysql高级5-SQL优化

    一.插入数据优化 1.1 批量插入 如果有多条数据需要同时插入,不要每次插入一条,然后分多次插入,因为每执行一次插入的操作,都要进行数据库的连接,多个操作就会连接多次,而一次批量操作只需要连接1次 1 ...