SAM学习笔记
SAM学习笔记
后缀自动机(模板)NSUBSTR(Caioj1471 || SPOJ 8222)
【题意】
给出一个字符串S(S<=250000),令F(x)表示S的所有长度为x的子串中,出现次数的最大值。求F(1)..F(Lengh(S));
【输入格式】
一个字符串
【输出格式】
依次输出答案
【样例输入】
ababa
【样例输出】
3
2
2
1
1
【算法分析】
相信大家在学习这个专题时已经接触过很多有关解决字符串问题的其它算法了。但是我们接下来要学的这个专题对于解决字符串的一系列难题都是一件利器----SAM后缀自动机(单词的有向无环图)。
为啥要学习这个算法呢?因为使用后缀自动机来解决字符串的问题很多情况下都可以在线性时间内解决。也就是O(n)差不多啦~(关于具体证明可以参考国家集训队陈立杰的后缀自动机论文)
后缀自动机的基本概念:
能够接收一个串S的所有后缀,可以包含所有S子串的信息,并且它的状态数最少的有向无环图。如果从初始状态root由任意路径走到一个状态(点),按顺序写出所有经过的字符,最后得出来的必然是原串的某一子串;如果走到了终止状态,那么这时所形成的必然是后缀。(建成如下)
再简单粗暴的概括一下大致做法:
假设当前建好了S的其中一个前缀的后缀自动机A,再添加一个字符x,那么就应该得到S的另一个前缀Ax的后缀自动机。像这样按顺序一个一个插入,最后把所有的都建好了之后,就得到了字符串S的后缀自动机。
那么回过头来看我们就很容易发现,这个构造过程其实是在线的,可以在任意时刻询问当前S串的信息,也可以再次在末尾插入字符。(感觉好像树结构一样)不过删除的操作在这里时不支持的。
基础数据结构:
直接上重点:right集合。这个东西在自动机构造里面并没有很明显的使用,但是right集合的中心思想也正是构造SAM的核心思路。
对于图中任意的一个点都可以(因为询问所经过的路径不同)表示成原串的一个或多个子串, 那么这个点的right集合就可以表示为由点为末尾组成的字符串后在原串中右端点(末尾)的位置。那么right集合的重要性主要体现在构造时对这个核心思路的使用;次要的作用就是,我们可以通过right集合的大小,求出某个子串在原串中出现的次数(对于给出的模板题就大有用处了)。
last:上一个建立的节点。
deep[ ]:询问到某个节点所能够经过的最大长度。
ch[ ].son[ ]:(son[i]表示一个字符)表示此节点是否能联通某个字符。
fail[ ]:这个数组也至关重要。先将定义,fail[i]表示i这个节点上一次出现的位置,也就是当前right集合位置的前一个。返回的节点也是上一个可以接收新节点的节点(如果当前节点可以接收成功,那么所返回的节点一定也可以)。
具体做法:
为了让大家更好的理解接下来的构造方法,这里给大家疏通一些主要性质。
1、从初始状态(用root表示)往后询问,到任意节点p的所有路经上所组成的字符串,都是子串之一。(根据这个性质,还可以推出性质2)
2、如果到节点p可以成为新后缀,那么从root到任意节点p(要注意和p相同的节点的存在)的每条路经所组成的所有字符串,都是后缀。
3、如果当前字符结点p可以接收新字符成为后缀,那么p的fail指向的结点也可以接收后缀,反过来就不行。
下面讲正式做法:
现在要插入字符x,也就是把A的后缀自动机变为Ax的后缀自动机。我们把要插入的节点储存为np,找到last,让last不断找fail(直到有x儿子或root才停止寻找)。假设当前fail找到了p节点(还没有停止),如果这个p没有x儿子,那么我们就把x的儿子赋值为np。停止寻找后,我们就要处理当前跳到的有x儿子的p(这个p是最终的)了。
处理的时候会出现两种情况。(假设p的x儿子是q节点)
1、deep[q]=deep[p]+1;也就是说q是从p的路径上直接过来的,p和q直接没有其他字符。那么q节点原本不一定可以接收新字符成为后缀,但p可以接收后缀x,如果当前经过p直接来到q,就可以看作是在A的某个后缀后面插入了x(现在q就是那个x),并且在下一次插入的时候,q也可以接收后缀(因为它现在可以被视为x的结点了),所以就把np的fail指向q。
2、另外一种情况显然就是p和q之间仍然存在其它字符,如果仍然按照情况1来假设,那么就不一定可以保证性质2了。其实也并不难解决,给他一个万能节点nq,nq的作用就是用来替代q的大部分功能,让nq和p连接成为p的儿子,把这种情况模仿成情况1相同的做法(deep[nq]==deep[p]+1)。
看图解吧!以下是完整的构造图解(实边是son边,虚边是fail):
构造的字符串为ACADD:
(1)插入A

(2)插入C:节点C的fail只能是根,所以C的fail指向root,deep=2。 注意在寻找fail的过程中,root和A都和C相连。

(3)插入A:节点A找last,也就是C,然后找到p指针所在的root,判断是情况1还是2,显然是1。直接进行操作。

操作后新插入的new A节点的fail就是root的儿子节点A(第一个),那第一个A就有了两个身份,1是后缀A的末尾字符,2是后缀ACA的末尾字符(代替了new A,保证状态最简,root就不用和new A相连)。

(4)插入D(情况1)

(5)插入D(情况2)

确定了是情况2,那么就要建立nq节点,图中可以很好的体现nq代替q的大部分功能(节点q的指针给nq,nq的fail改为p,q和np的fail改为nq)。

最后,从当前p开始根据fail边往上跳,边把问到的儿子为q的结点的儿子改为nq。(实际上就是保证性质2成立)

BINGO!~终于完成啦~~~
PS: 感谢大佬functioner%%%
学会了后缀自动机,开始做题吧:
这道模板题其实很简单,主要是利用了right集合的性质,很容易就可以想到一个子串right集合的大小其实就是出现的次数,dp求一下就ok啦。
【参考程序】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define N 500005
#define ll long long
using namespace std;
char s[N];
int root,cnt,last;
int a[N],Rsort[N],f[N],sa[N],fail[N],deep[N],r[N];
struct SAM
{
int son[];
}ch[N];
void add(int k)
{
int x=a[k];
int p=last,np=++cnt;//上一个节点,新节点
deep[np]=k;
while(p> && ch[p].son[x]==)ch[p].son[x]=np,p=fail[p];
//不停往上跳p,如果我跳到的节点包含当前字符 停止
if(p==)fail[np]=root;//如果我没有另一条返回的路径,那么就直接指向根
else
{
int q=ch[p].son[x];//q记录已选择的返回节点
if(deep[p]+==deep[q])fail[np]=q;//如果q和p正好在一条路上(深度比q小1),直接指向它
else//否则...GG~~ (建一个万能的新节点)
{
int nq=++cnt;//新节点
deep[nq]=deep[p]+;//这个新节点和p节点同一路径,深度+1
ch[nq]=ch[q];fail[nq]=fail[q];//替代q的功能
fail[np]=fail[q]=nq;//让新节点和可选择的节点都指向nq
while(ch[p].son[x]==q)ch[p].son[x]=nq,p=fail[p];//维护 让nq替代q大部分的功能
}
}
last=np;
}
int main()
{
scanf("%s",s+);
last=root=++cnt;
int len=strlen(s+);
for(int i=;i<=len;i++)a[i]=s[i]-'a';//转化为数字,为什么???
//答:“因为要放进数组啊!”
for(int i=;i<=len;i++)add(i);//建立SAM
//将深度基排
for(int i=;i<=cnt;i++)Rsort[deep[i]]++;
for(int i=;i<=len;i++)Rsort[i]+=Rsort[i-];
for(int i=;i<=cnt;i++)sa[Rsort[deep[i]]--]=i;//sa表示深度排名为i的位置 //处理right集合
for(int i=,p=root;i<=len;i++)p=ch[p].son[a[i]],r[p]++;//从小到大按顺序处理子节点
for(int i=cnt;i;i--)r[fail[sa[i]]]+=r[sa[i]];//反过来更新父亲 //dp f[i]就是最终答案
memset(f,,sizeof(f));
for(int i=;i<=cnt;i++)f[deep[i]]=max(f[deep[i]],r[i]);
//从小到大枚举所有的点,每个点的deep值代表一个字串长度,用right集合更新
for(int i=len;i;i--)f[i]=max(f[i+],f[i]);//维护一下正确答案 for(int i=;i<=len;i++)printf("%d\n",f[i]);
return ;
}
SAM学习笔记的更多相关文章
- 【文文殿下】后缀自动机(Suffix Automaton,SAM)学习笔记
前言 后缀自动机是一个强大的数据结构,能够解决很多字符串相关的(String-related)问题. 例如:他可以查询一个字符串在另一个字符串中出现的所有子串,以及查询一个字符串中本质不同的字符串的个 ...
- 后缀自动机SAM学习笔记
前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...
- 后缀自动机(SAM) 学习笔记
最近学了SAM已经SAM的比较简单的应用,SAM确实不好理解呀,记录一下. 这里提一下后缀自动机比较重要的性质: 1,SAM的点数和边数都是O(n)级别的,但是空间开两倍. 2,SAM每个结点代表一个 ...
- SAM学习笔记&AC自动机复习
形势所迫,一个对字符串深恶痛绝的鸽子又来更新了. SAM 后缀自动机就是一个对于字符串所有后缀所建立起的自动机.一些优良的性质可以使其完成很多字符串的问题. 其核心主要在于每个节点的状态和$endpo ...
- <老友记>学习笔记
这是六个人的故事,从不服输而又有强烈控制欲的monica,未经世事的千金大小姐rachel,正直又专情的ross,幽默风趣的chandle,古怪迷人的phoebe,花心天真的joey——六个好友之间的 ...
- Jade学习笔记
初学nodejs,折腾过用handlebars做模板,后来隔了一段重新学习,用了jade,真心简洁……记录一些学习笔记,以备复习. jade是基于缩进的,所以tab与space不能混用: 属性的设置: ...
- 《C++ Primer Plus》学习笔记6
<C++ Primer Plus>学习笔记6 第11章 使用类 <<<<<<<<<<<<<<<&l ...
- Java8学习笔记----Lambda表达式 (转)
Java8学习笔记----Lambda表达式 天锦 2014-03-24 16:43:30 发表于:ATA之家 本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘.因为本人 ...
- JavaSE中Collection集合框架学习笔记(2)——拒绝重复内容的Set和支持队列操作的Queue
前言:俗话说“金三银四铜五”,不知道我要在这段时间找工作会不会很艰难.不管了,工作三年之后就当给自己放个暑假. 面试当中Collection(集合)是基础重点.我在网上看了几篇讲Collection的 ...
随机推荐
- Delete, drop table, truncate之间的区别
Delete, drop table, truncate有什么区别? delete 删除表中数据,可以删除一条或多条记录,可以回滚,记录操作日记,是DML truncate table,一次性清空表中 ...
- HBase编程 API入门系列之delete(管理端而言)(9)
大家,若是看过我前期的这篇博客的话,则 HBase编程 API入门之delete(客户端而言) 就知道,在这篇博文里,我是在客户端里删除HBase表的. 这里,我带领大家,学习更高级的,因为,在开发中 ...
- WCF WEB HTTP请求 WCF REST FUL
首先上点概念WCF 很好的支持了 REST 的开发, 而 RESTful 的服务通常是架构层面上的考虑. 因为它天生就具有很好的跨平台跨语言的集成能力,几乎所有的语言和网络平台都支持 HTTP 请求, ...
- 安装android驱动解决device not found
安装android驱动,解决device not found 0.1问题环境 windows10,android studio /eclipse,sdk ,jdk(其他windows也可用) 1.0 ...
- Android 微信分享与QQ分享功能
微信分享与QQ分享功能现在都挺常见的,可以根据一些第三方社会化分功能快速实现,不过多多少少都不怎么纯净,最好都是自己看官方文档来实现就最好了~ 一.微信分享 微信分享功能需要先在微信开放平台注册应用并 ...
- 面试官:精通 Mybatis?请回答下这几个问题
点关注,不迷路:持续更新Java架构相关技术及资讯热文!!! Mybatis是现在非常主流的持久层框架,虽然平时用的多,但是其中几个细节的问题,能说出个所以然来不? 一.最常见,参数中 #{} 和 $ ...
- MySQL数据表查询操
准语法结构:编写DQL时一定要严格按照此语法的顺序来实现!/* SELECT [ALL | DISTINCT] ALL表示查询出所有的内容 DISTINCT 去重 {* | 表名.* | 表名.字段名 ...
- Linux搭建Jenkins
1.添加存储库 yum的repo中默认没有Jenkins,需要先将Jenkins存储库添加到yum repos,执行下面的命令: 使用wget -O下载并以不同的文件名保存 [root@besttes ...
- Python笔记7----Pandas中变长字典Series
1.Series概念 类似一维数组的对象,由数据和索引组成 2.Series创建 用Series()函数创建,0,1,2为series结构自带的索引. 可以自己指定索引值,用index,也可以直接用字 ...
- ASP 读取Word文档内容简单示例
以下通过Word.Application对象来读取Doc文档内容并显示示例. 下面进行注册Word组件:1.将以下代码存档命名为:AxWord.wsc XML code复制代码 <?xml ve ...