SAM的性质与应用

鸣谢clover_hxy学姐

之前介绍过 SAM S A M 的构建
打算做点题练习一下,但是找到 SAM S A M 的裸题之后,根本不知道怎么用 SAM S A M
所以就来总结一下 SAM S A M 的应用

找不同的子串的个数

方法一:用dfs处理处每个点能扩展出多少个字符串 sum[x]=sum[t[x].son[i]]+1 s u m [ x ] = ∑ s u m [ t [ x ] . s o n [ i ] ] + 1 ,其实可以不用dfs,拓扑一下(按len从小到大)然后倒着做。最后sum[1]就是所有子串的个数。
方法二: ans=t[x].dist[t[x].fa].dis a n s = ∑ t [ x ] . d i s − t [ t [ x ] . f a ] . d i s

两个串的最长公共子串

这个问题的一种经典解法就是DP
f[i][j]=f[i1][j1]+1(a[i]==a[j]) f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + 1 ( a [ i ] == a [ j ] )
f[i][j]=0(a[i]!=a[j]) f [ i ] [ j ] = 0 ( a [ i ] ! = a [ j ] )

另一种解法就是后缀数组
我们把两个串接起来,得到 SA S A 数组和 height h e i g h t 数组
SA[i] S A [ i ] SA[i1] S A [ i − 1 ] 分别属于两个串的 height h e i g h t 的最大值即可

那么利用 SAM S A M 要怎么解决此问题呢?
我们用 A A 串建立 SAM,然后用 B B 串在后缀自动机上匹配,
因为后缀自动机中包含A串的所有子串,并且 root r o o t 到后缀自动机中的任意结点形成的路径都是 A A 的合法路径,所以 B串能匹配到的点到根的路径都是 AB A B 的最长公共子串

那么我们只要找到所有能匹配的结点到根的最远距离即可

如果匹配到一个点后匹配不上了怎么办呢?
后缀自动机中有一个很重要的指针—— parent p a r e n t 指针
每个点的 parent p a r e n t 指针指向的是上一个可以接受后缀的结点,即如果当前节点可以接某个后缀那么 parent p a r e n t 指针指向的结点也一定可以接受
可以将 parent p a r e n t 指针指向的位置表示的状态看成该状态的一个后缀(有点像失配指针)
那么匹配不上我们就不停的跳 parent p a r e n t 链,直到匹配上或者回到根节点为止
跳到一个节点后当前匹配的长度就变成了 len[i] l e n [ i ] (i指可以匹配上的节点)

例题

cv3160 —> 题解

多个串的最长公共子串

这道题可以二分答案,用后缀数组做
(一听这个做法,就觉得比较麻烦)

那如果我们借助后缀自动机呢?
还是对第一个串建立 SAM S A M ,不过这次匹配的时候是多个串匹配
每次匹配的时候对于后缀自动机中的每个结点维护一个值 l l ,表示的是到达该结点所能匹配上的最大长度
(每个结点都可能是多个结点的儿子,所以从根到该点的路径长度可能是不同的)

光匹配还不够,我们需要按照拓扑序倒序(按照dis从大到小),用每个结点去更新ta的parent节点
因为如果匹配到一个状态,那么实际上他 parent p a r e n t 链上的所有状态都匹配上了
l[fa[i]]=dis[fa[i]] l [ f a [ i ] ] = d i s [ f a [ i ] ] dis d i s 是到达每个结点的最长路径)
然后对于每个串匹配后得到的每个位置的 h h 数组取min得到数组 g g g的最大值极为答案

例题

spoj 1812 —> 题解
bzoj 2946 —> 题解

第K小子串问题

这类问题多与拓扑序有一定的关系
我们按照拓扑序倒序(按照dis从大到小)用每个点取更新 fa f a 的取值,就可以得到每个位置后面有多少个子串
然后在后缀自动机上进行 dfs d f s ,每次从字典序小的开始计算,
如果当前子树(其实不是严格的树形结构,但是我们可以形象的这样理解)的size>=k就说明第k小的子串的结尾在该子树中
否则k-size,然后向下寻找
有点像主席树查询区间k大

例题

spoj 7258 —> 题解
bzoj 3998: [TJOI2015]弦论 —> 题解
bzoj 2882: 工艺 —> 题解

重复出现的子串问题

这类问题一般都与 right r i g h t 集合有关系,
所谓 right r i g h t 集合就是某个状态或者说是子串 str s t r s s 中每次出现位置的右端点组成的集合|right|常用来表示 right r i g h t 集合的大小

那么 right r i g h t 的大小怎么求呢?

right(i) r i g h t ( i ) right(fa[i]) r i g h t ( f a [ i ] ) 的子集,所以我们按照 parent p a r e n t 树中深度从大到小,依次将每个状态的 right r i g h t 集合并入 fa f a 状态的 right r i g h t 集合,初始的时候只有主链上的 |right|=1 | r i g h t | = 1

按照这种方式我们不仅可以求出 right r i g h t 集合的大小,还可以求出某个子串在字符串中出现的最靠左最靠右的位置等等,在此不在赘述

这类问题静态的比较好搞,如果是动态的可能需要借助数据结构进行维护,比如下面的bzoj2555就需要用到LCT动态维护 parent p a r e n t 树的形态,并维护节点的信息

例题

poj 1743: Musical Theme —> 题解
spoj 8222: NSUBSTR-Substrings
bzoj 2555: SubString —> 题解

后缀自动机与DP的结合

一般这类题中的后缀自动机是用来做预处理的,核心是DP
所以说:浔阳DP无音乐,终岁不闻AC声

例题

bzoj 4180: 字符串计数 —> 题解
bzoj 4032: [HEOI2015]最短不公共子串 —> 题解
bzoj 2806: [Ctsc2012]Cheat —> 题解
bzoj 3238: [Ahoi2013]差异

广义后缀自动机

一般有两种形式
一种是对 trie t r i e 建立广义的后缀自动机
另一种是对多个独立的串建立广义的后缀自动机

对于trie树:我们在只有一个串的时候 p p 就是直接指向前一个字符的位置,因为我们现在是树结构,所以p指向的应该是该点在 trie t r i e 树中父节点的位置,剩下的建立过程与普通的后缀自动机相同
对于多个串:每次加完一个串就把 p p 回到root
然后自然加入即可

例题

bzoj 3926: [Zjoi2015]诸神眷顾的幻想乡 —> 题解
bzoj 2780: [Spoj]8093 Sevenk Love Oimaster —> 题解
bzoj 3277: 串
bzoj 3473: 字符串

right r i g h t 集合与 parent p a r e n t

right r i g h t 集合与 parent p a r e n t 树是后缀自动机最常用也是最好用的两个东西
right r i g h t 集合一般用来处理计数问题,两者相辅相成不可分离

有一个比较有用的性质:两个串的最长公共后缀,位于这两个串对应状态在 parent p a r e n t 树的最近公共祖先上
例题

bzoj 4516: [Sdoi2016]生成魔咒
bzoj 4566: [Haoi2016]找相同字符
bzoj 1396: 识别子串

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值