后缀自动机SAM BZOJ 2806
终于遇到了一道后缀数组不能过 一定要学SAM的题。。。
(看了半个下午+半个上午)
现在总结一下(是给我自己总结。。所以只总结了我觉得重要的 。。
看不太懂的话可以To http://blog.csdn.net/clover_hxy/article/details/53758535 图文并茂
或者 去看更长更详细的陈立杰PPT http://wenku.baidu.com/link?url=9YEHHchtr0vyGGDZAcsMYPI3l_Q82UNPuS4KqkfrlG_t5NFk_9iXZd86Uq4uDqqLUKFJL7ZINkxQbstwqaF6OEFes3elFsXmbDZVIsgVwti:
SAM Suffix AutoMaton [AutoMaton的定义 不懂自己查吧 ]
首先 (N为 字符串长度*2) 最基本的SAM 要开的数组 (其含义与用处在之后说明):pr[N] (别人的代码版本还有fa、pre、link...变量名而已 别在意), ch[N][26(当然不一定是26个字母)],
ma[N](意思是maxlen,别人的有step、len...),还要cnt和last两个全局变量,p,np,q,nq 四个局部变量。 root就设为1,所以不需要开个变量rt。
SAM为什么能用O(n)的空间存下n^2的子串? 我是这样理解的{
每个节点 对应的是 一个独一无二的right集合
right集合是什么?【比如说 一个S字符串为 AABABA ,子串BA有2个 对吧 .所有子串BA 对应的右端点集合(即right集合)为{4,6},子串ABA也有2个 ,right集合也是{4,6}】
那么 BA和ABA的right集合一样 BA和ABA 被自动机从rt状态 一步一步转移 最终到达同一个节点
设这个节点 编号为v,v不仅对应了 {4,6}这个right集合 同时还对应了 子串BA和ABA 可以看出 v代表的子串 其右端点是 同时属于 集合{4,6}的 【什么叫同时属于? 看看AABA,它的右端点只能是4,不能是6】,还可以看出 只有长度为2~3的子串可以 满足“同时属于” ,【如长度为1的A, 它不仅属于{4,6} 它属于{1,2,4,6}; 长度为4的AABA,它只属于{4}——————所以它们属于另外的节点】
这里长度为2~3可能不是很明显 但其实可行的长度 一定是连续的min~max 想想就明白了。。
[v节点对应 的子串 长度为min(v)~max(v)] 这个信息由 ma[]存储,ma[v]=max(v) 但它只存储了最大值。 最小值呢? [这里先提一下 ,min(v) 是 ma[pr[v]]+1,接下来会说。。
那么我们来证明节点数是 O(N)的 (准确说是 小于 length(S)*2)
先来看 上面那个字符串AABABA , 对它建出的SAM有 个节点,通过pr[] 构成一棵树,最好在草稿纸上画个图
以下一一说明:
1号 即root节点(我简写成rt) 对应right集合{1,2,3,4,5,6} 长度是 0~0 pr[1]=0
↙以下x号只是我给节点标个号,可能真正的数组下标不是这样的,不要在意,但1号一定是1。
2号 {1,2,4,6} 长度是 1~1 对应子串:A,A,A,A pr[2]=1
3号 {3,5} 长度1~2 pr[3]=1 对应:B,B,AB,AB
4号 {2} 长度2~2 pr[4]=2 对应:AA
5号 {4,6} 长度2~3 pr[5]=2 对应:BA,BA,ABA,ABA
6号 {3} 长度3~3 pr[6]=3 对应:AAB
7号 {5} 长度3~5 pr[7]=3 对应:BAB,ABAB,AABAB
8号 {4} 长度4~4 pr[8]=5 对应:AABA
9号 {6} 长度4~6 pr[9]=5 对应:BABA,ABABA,AABABA
从根到叶子 就是对集合{1,2,...,n}不断地分割成至少两个部分
可以看出来
*叶子节点最多n个 而且每个非叶子节点都有分叉导致right集合不断变小 所以显然空间是O(N)的
*再数一下 对应的子串 总共是 6*(6+1)/2=21个,它们包含了S的所有子串
*对于一个节点v,它的right集合有k个元素,长度范围是min(v)~max(v)
*观察 min(v)=ma[pr[v]]+1 这条性质,好好理解一下 长度越小的串在S中出现次数越多 所以right集合越大,
所以 从根到叶子 的right集合不断变小,而min和max在增加。。 这样就明白pr[]是什么了吧。
}
那么现在讲 ch[v][c],挺好理解 ,就是 从v节点 再读入一个c字符(字符已经转成int了)之后会转移到 的节点
所有节点有ch[v][c]构成一个拓扑图 注意 这个拓扑图的结构 与pr[]连成的树 毫无关联(但是节点集合是相同的)。 只是两者之间满足了一些性质(之后讲)
先上一下代码 对字符串S建出SAM (是一个字符一个字符添加的)
int add(int p,int c){
if (ma[p]+==ma[ch[p][c]]) return ch[p][c];
int np=++cnt,q,nq,fl=ch[p][c]; ma[np]=ma[p]+;
while (p&&!ch[p][c]) ch[p][c]=np,p=pr[p];
if (!p) pr[np]=; else{
q=ch[p][c];
if (ma[q]==ma[p]+) pr[np]=q; else{
nq=fl?np:++cnt; ma[nq]=ma[p]+;
pr[nq]=pr[q]; pr[q]=nq;
if (!fl) pr[np]=nq;
memcpy(ch[nq],ch[q],sizeof ch[q]);
while (ch[p][c]==q) ch[p][c]=nq,p=pr[p];
}
}
return np;
} //与fl相关的是广义后缀自动机
Add
不知道前面说的清不清楚 但是看到这段代码 大多数人第一反应 应该是一脸懵逼的。。
...今天先写到这里、、、好累。。。【可能再也不补了。。 2017.3
现在我来(面对程序)解答疑问(如果你疑问很多 不要像我一样死钻一个问题。 你可以先看完,万一能解决前面的疑问。。):
& cnt,last是全局变量,cnt就是编个号,不多说。。last是上一次add操作后,加入的那个节点(注意不一定是cnt-1);
& p=last ,临时变量, np是new p,新加入的节点, q 和nq不一定每次都用的到,nq是用来复制q的 "new q".
& 显然新加入的这个点max为上一个点的max+1,因为在这个len下,一定可以区分出它这个Right集合。
& add操作,要做的操作是: 更新 某些点的ch[][c], 求出pr[np], 既然max[np]已经确定了, 那么要求 ma[pr[np]]+1是min(np), 且pr[np]的Right集合包含np的Right集合
以下设S'为已经加入的长度为n的字符串,现在要再加入一个字符c, 设S=S'+c 长度为n+1
& p=last , 显然,只有p,pr[p],pr[pr[p]]..的Right集合中有n, 对应子串中含有S‘ 的后缀(每个len对应都有一个S‘的后缀) ,所以只对 p及其一连串pr 的ch[][c] 添加np。
& 如果p走到顶了 还没有ch[p][c] ,那么,说明字符c第一次出现,所以当len为1时,就可以区分出字符c这条分支, 即minlen=1 pr[np]当然为1;
& 否则就是 当前走到的p ,有ch[p][c]了。 对于之前 ch[][c]=0的 ,都赋为np了, 相当于 np中已经加入了 “长度ma[p]+1~ma[last]的串 + c” 对应的子串(都是S的后缀) 了。
现在设q为ch[p][c] 分类讨论:
case 1 ma[q]=ma[p]+1, 也就是q中的子串最长也就到ma[p]+1, 它在ma[p]+1下,与我们要加入的“ma[p]的串+c”是一样的 无法区分,所以将信息并入这个节点。
这样min[np]就定为了ma[p]+2,即ma[q]+1, 也就是len<ma[q]+1的串都已经无形的加入了q,pr[q],pr[pr[q]]... 所以我们要做的只剩下 pr[np]连向q了。
case 2 ma[q]>ma[p]+1, q中最长的子串超过ma[p]+1, 它只有在ma[p]+1时, 与要加入的 “ma[p]的串+c”是无法区分的 ,所以新建一个节点nq,来记录ma[p]+1时的信息,
而大于ma[p]+1时的信息,则区分成两个分支,一个是延续原来q中大于ma[p]+1的那些信息(pr[q]=nq), 另一个是接字符c信息(pr[np]=nq). 然后把ch信息中的q全改为nq 即可(同样只有p,pr[p]..的ch中含有q,所以继续将p往上)。
【2017.7 上文下文 都有一些更新、】
来补之前没讲到的一些东西:(由于是时隔多日补的,条理会有些乱)
1. SAM最大的应用是用来匹配 任意串S在 模式串A中最大的匹配后缀,(即 S最长的能与A的字串匹配的 后缀) ,
匹配的步骤是: 可以成功多匹配一个字符c时,当前匹配最长长度k 加1,当前匹配到的节点p 跳到 ch[p][c];否则 p 要跳到pr[p],匹配长度k 变为ma[p]。
2. ch[p][c]表示p节点 接c后 将会匹配到的 节点。 如果到了p节点,当前能匹配的最大长度为k,那么k一定在当前节点p的min~max间,且当前已匹配上的串 一定在p节点对应的子串集中。
走到ch[p][c]后, 当前匹配上的串后面接了c, k加了1, 一样满足上一行的性质。
下面是题目了。。
但是开头说的那道题有版权,这里不放了。
另外有一道BZOJ 2806,是CTSC的题也不错。 后缀自动机+二分 单调队列DP。
在建SAM的时候,在每个标准文章结尾再add(2),就可以把这些串穿起来了
注意不要用浮点 会被卡精度
#include <bits/stdc++.h>
using namespace std;
int L,R,j,last,k,t,N,M,n,ch[][],ma[],pr[],d[],v[],f[]; char S[];
void add(int c){
int p=last,np=++k,q,nq; last=np; ma[np]=ma[p]+;
while (p&&!ch[p][c]) ch[p][c]=np,p=pr[p];
if (!p) pr[np]=; else{
q=ch[p][c];
if (ma[q]==ma[p]+) pr[np]=q; else{
nq=++k; ma[nq]=ma[p]+;
memcpy(ch[nq],ch[q],sizeof ch[q]);
pr[nq]=pr[q]; pr[q]=pr[np]=nq;
while (ch[p][c]==q) ch[p][c]=nq,p=pr[p];
}
}
}
void play(){
k=; t=; int c;
for (int i=;i<=n;v[i++]=t){
c=S[i]-'';
if (ch[k][c]) k=ch[k][c],++t;
else{
while (k&&!ch[k][c]) k=pr[k];
k?(t=ma[k]+,k=ch[k][c]):(t=,k=);
}
}
}
bool jud(int M){
int l=,r=;
for (int i=;i<=n;++i){
f[i]=f[i-];
if (i-M>=){
while (l<=r&&f[d[r]]-d[r]<=f[i-M]-i+M) --r;
d[++r]=i-M;
}
while (l<=r&&d[l]<i-v[i]) ++l;
if (l<=r) f[i]=max(f[i],f[d[l]]+i-d[l]);
}
return f[n]*>=*n;
}
int main(){
scanf("%d%d",&N,&M); last=k=;
while (M--){
scanf("%s",S); n=strlen(S);
for (int i=;i<n;++i) add(S[i]-'');
add();
}
while (N--){
scanf("%s",S+); n=strlen(S+);
play(); L=; R=n;
while (L<R){
j=L+R+>>;
jud(j)?L=j:R=j-;
}
printf("%d\n",L);
}
return ;
}
Gasai Yuno
2018年初。。再次改动
【这次版子算完整了吧?。。】
现在的这个广义后缀自动机版子,解决了其它版子容易出现的一些问题。
1. 不会出现maxlen[pre[x]]==maxlen[x]的情况 。 这样就支持了按maxlen排序,不再 必须要拓扑排序了。
2. 也不会出现一些节点不会被走到的情况。 这样就不需要在建完后重新跑一边,来求每个trie树点对应的SAM上的节点了。 只要记下每次add操作的return值就好。
3. 支持 在有重复字符出边的假trie树上 建。
注意,广义后缀自动机要bfs建图。不然可能会被刻意卡TLE。(比如梳子图,直链上都是a,刺上都是b 。dfs可能会被卡n2)
后缀自动机SAM BZOJ 2806的更多相关文章
- [转]后缀自动机(SAM)
原文地址:http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html 感觉自己看这个终于觉得能看懂了!也能感受到后缀自动机究竟是一种怎样进行的数据结构了. ...
- 【算法】后缀自动机(SAM) 初探
[自动机] 有限状态自动机的功能是识别字符串,自动机A能识别字符串S,就记为$A(S)$=true,否则$A(S)$=false. 自动机由$alpha$(字符集),$state$(状态集合),$in ...
- SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)
1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...
- 后缀自动机SAM学习笔记
前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...
- 浅谈后缀自动机SAM
一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...
- 后缀自动机(SAM)奶妈式教程
后缀自动机(SAM) 为了方便,我们做出如下约定: "后缀自动机" (Suffix Automaton) 在后文中简称为 SAM . 记 \(|S|\) 为字符串 \(S\) 的长 ...
- 【算法】后缀自动机(SAM) 例题
算法介绍见:http://www.cnblogs.com/Sakits/p/8232402.html 广义SAM资料:https://www.cnblogs.com/phile/p/4511571.h ...
- 【算法专题】后缀自动机SAM
后缀自动机是用于识别子串的自动机. 学习推荐:陈立杰讲稿,本文记录重点部分和感性理解(论文语言比较严格). 刷题推荐:[后缀自动机初探],题目都来自BZOJ. [Right集合] 后缀自动机真正优于后 ...
- 后缀自动机(SAM) 学习笔记
最近学了SAM已经SAM的比较简单的应用,SAM确实不好理解呀,记录一下. 这里提一下后缀自动机比较重要的性质: 1,SAM的点数和边数都是O(n)级别的,但是空间开两倍. 2,SAM每个结点代表一个 ...
随机推荐
- Java_AOP原理
AOP : 面向切面编程 在程序设计中,我们需要满足高耦合低内聚,所以编程需满足六大原则,一个法则. AOP面向切面编程正是为了满足这些原则的一种编程思想. 一.装饰者模式: 当我们需要给对象增加功能 ...
- 使用Crypto对数据进行加密解密
注释都在代码里: 先撸客户端: from Crypto.Cipher import AES import base64,requests class Message(object): def __in ...
- TreeView 与 ListView
ListView: viewStyle icon 大图标 list 列表,单列 report 报表 smallIcon 小图标 largeImage 与icon对应 smallImage 与saml ...
- Java全局变量不加修饰符时的访问权限范围
如上图所示.
- excel 分类汇总函数
1.先用数组公式对单元格区域 B3:B39 ,进行提取去重复非空调单元格信息.单元格B52数组公式: =INDIRECT(TEXT(MIN(IF((COUNTIF(B$51:B51,B$3:B$39) ...
- centos下的hadoop服务器的配置
是我安装CentOS服务器的过程,记录下来,与大家一起分享. 安装操作系统 CentOS 6.2 ,CentOS-6.2-i386-bin-DVD1.iso(32位) ,CentOS-6.2-x86_ ...
- 【转载】Http协议与TCP协议简单理解后续
写了这么长时间的代码,发现自己对TCP/IP了解的并不是很透彻.虽然会用C#的HttpClient类来进行网络编程,也可以使用Chrome的开发者工具来检测每一次的HTTP请求的报文头与报文体,也知道 ...
- 理解CSS中的BFC(块级可视化上下文)[译]
开篇 一些元素,如float元素,如position为absolute,inline-block,table-cell或table-caption的元素,以及overflow属性不为visible的元 ...
- 【IOS工具类】IOS9的CoreSpotlight(OC语言)
什么是CoreSpotlight?就是在IOS9下.让用户在下拉的搜索页面里能够搜索到你的应用. #import <Foundation/Foundation.h> @interface ...
- mysql insert操作
insert的语法 INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] [INTO] tbl_name [(col_name,...)] ...