SAM-后缀自动机-个人总结
JZ入门题:在SAM上跑SG函数
感觉SAM写起来比AC自动机还简单
然后考试的时候就忘了AC自动机
写SAM的时候细节还是挺多的,一定要随时用眼睛看
SAM初始的tot要设为1!!!
看12月集训Day2.
因为很重要,所以写在前边。
设k为字符集大小。
普通的SAM(一个串的SAM)
如果每个点的trans数组开成字符集大小:时间
此时:
SAM中的状态总数(right集合相同为一个state)上界:
SAM中的转移总数上界:
出处
如果k很大,使用map存trans:时间
但是如果使用主席树实现
GSAM-广义SAM(对Trie建)
PDF或者看结尾
当字符集的大小是不可忽略的时候。不能在Trie上建SAM
好我们看另一篇:
k为字符集。
用BFS离线建: 时空
用DFS在线建: 时间
SAM的处理,往往和可持久化线段树合并维护right集合有关
把所有子串按照字典序大小输出
建好SAM后直接按字典序dfs一遍。
求字典序第k小子串
SPOJ SUBLEX
重复的子串不多次计数。
注意到后缀自动机中,虽然一个结点可能被当成多个结点的儿子,但这些连边都是满足拓扑序的,就是说,可以预处理出到达某个结点时,往下走可以得到多少个字符串,这样的预处理用拓扑排序+递推即可。这样的话,询问就是O(n)的复杂度。
在写这道题时,我在纠结如何预处理这个size,甚至想要去重,但是因为一条路径就唯一对应一个子串,因此我们统计的其实是一个点之后的路径条数,直接拓扑排序+DP即可。
但如果重复的子串多次计数呢?[TJOI2015]弦论
很简单,之前的方法是从每个节点必有一个子串产生,而现在每个点有多个子串产生,是多少?是right集合的大小!于是同样的做法就行了。
size不要和上一个算法弄混!求right集合时只给主干点+1。
一定要按拓扑序DP!
记得加上每个点的贡献。跑答案的时候,记得每走过一个边就要--k,因为有一个子串在这里结束了。
求多个串的最长公共子串
首先选出第一个串,建立SAM,然后我们要把其他串放到SAM上跑,方式和AC自动机很像,我们把parent树看作fail树,如果这个点没有这个儿子,就跳fa,同时更新len。
然后如果跳到了有这个儿子的节点,就++l(l表示已经匹配的长度),x变成儿子;
否则从rt开始重新跑。
然后考虑多串:我们其实是把每个点的匹配长度取min之后就是答案。于是我们维护每一个串的匹配的最长长度,然后和len和之前的min取min。
for(ri i=1; i<=n; ++i)
{
int t=s[i]-'a';
while(x&&!ch[x][t]) x=fa[x],l=len[x];
if(x) ++l, x=ch[x][t];
else x=rt,l=0;
mx[x]=max(l,mx[x]);
}
字符串的最小表示法
但是明明有更好写的双指针啊,而且不在意字符集大小。
大字符集非要用SAM?map!嫌弃map的复制太慢?主席树弃疗!
求本质不同的子串数量
就是每个点的
字符串题,多组数据有关数组一定要清空干净!最好字符串char数组也memset一下
求多个数字字符串本质不同的子串的数字和
hdu4436
多个字符串?把每个字符串用特殊字符(比如10)接起来,跑一边SAM,建出DAWG,然后考虑DP:
我们不能走10边;对于根节点不能走0边,因为忽略前导零
!然后维护从根节点到点x的方案数cnt和数字和sum,转移的话:
其实不用特殊字符接起来也行,直接建广义SAM跑就可以了,很快。
[AHOI2013]差异
一个很简单的方法是求出Height数组之后,这个问题就转化为了
如果非要用SAM怎么办?Parent树的LCA实际上是两个前缀的最长公共后缀,所以我们需要对反串建立SAM,然后两个点的LCA就是最长公共前缀,DP计数一下就可以了。
[APIO2014]回文串
开O2防MLE???
Manacher和SAM的优秀结合!
首先建SAM,然后跑Manacher,得到若干本质不同的回文子串,然后我们要查询它们的出现次数,怎么查?子串
[USACO17DEC]Standing Out from the Herd
好题,专门写一个。
[ZJOI2015]诸神眷顾的幻想乡
求树上所有路径的不同子串个数,叶子数量<=20,N<=1e5
本来想
SP8093
给定n个模板串,以及m个查询串,依次查询每一个查询串是多少个模板串的子串。
我们给SAM上每个点一个颜色,那么问题就转化成了一个点的parent树的子树的颜色个数,可以用dsu on tree或者树状数组+扫描线解决。
串
现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身)。
像上一题一样进行预处理,然后把每个串放在上面跑,如果一个点的次数小于k就不停的跳fa,然后答案累加上
由于一步只能向下走1,所以复杂度是有保证的。这道题我为了加快,写了一个倍增,挂掉了,很奇怪。
CF666E Forensic Examination
SAM+数据结构
CF700E Cool Slogans
思维好题
BZOJ3796 Mushroom追妹纸
见另一篇博客。
pty的字符串
先把S建出SAM,求出每个点的rigth集合的大小, 然后把Trie树放到SAM上跑,每经过一次一个点就把这个点的cnt++,然后求cnt在parent树上的子树和,答案即为
但是这个是不对的!因为匹配到一个点时,匹配的长度不一定是这个点的len,所以这个点的贡献要特殊计算,贡献的最后一部分为匹配的
实际写了一下,有很多需要注意的地方:
-
这不是Trie树!会有重边!
-
因为S太长了(8e6)所以必须对Trie建立SAM,吧S放在上面跑。
-
因为是广义后缀自动机,所以要拓扑排序。
[SCOI2012]喵星球上的点名
见另一篇博客。
CF1063F
一眼看上去是一个神题,但是用到的算法并不难。
我们把串反过来,序列也反过来。
首先,我们每次选出来的串,一定一个是另一个的后缀或前缀。换句话说,我们的序列从前到后长度依次递增。
设
我们发现,我们能够用来更新dp值的位置右端点也是单调不减的,于是用一个指针维护即可。
先丢定义:
结论:
说人话:
定义k阶GSAM为:最小的k使得取出原来所有串的后k个字符,它们两两不同。n为字符串个数。
则最简k-GSAM的时空复杂度均为
但是Trie树上的串,虽然两两的后缀都不同,貌似k=1,但是!按Trie建出来的不是最简的GSAM!因为会有很多重合的情况!况且Trie树也不是最简状态自动机。