附:本文参考(后缀自动机学习总结)[http://blog.sina.com.cn/s/blog_70811e1a01014dkz.html]
先感性认知一下
这是一个建好的 字符串ACADD的SAM 示意图
每一个节点要维护的东西如下
- Sigma
常数,表示字符的种类数,比如维护小写字母字符串时为26 - son[1 ~ Sigma](上图实线箭头) 子节点的指针
- link(上图的虚线箭头)
上一个可以接收后缀的结点
注意link不是该点的父结点!(因为一个点有可能是多个点的儿子) - len
从根结点走到当前点,最多需要多少步
SAM性质:
- 从root到任意结点p的每条路径上的字符组成的字符串,都是当前串t的子串.
- 因为满足性质一,所以如果当前结点p是可以接收新后缀的结点(被link指向),那么从root到任意结点p的每条路径上的字符组成的字符串,都是必定是当前串t的后缀.
- 如果结点p可以接收新的后缀,那么p的link指向的结点也可以接收后缀,反过来就不行.
可以推出
- 按照字典序遍历SAM可得到所有子串(是去重的 当然也可以通过记录节点的size来维护)
- 从最后加入的一个点(肯定是原字符串的末尾节点嘛)向前找link 找到的就是所有的后缀结尾
然后就是建图辽
由于每一步都是一样的
在这里直接从一半开始建
现在要插入一个新的点np
- STEP1
对于一个已经建了一半的SAM 找到它最后一个被插入的点p(因为这个点必然可接受后缀
然后用p向前跳link 直到被跳到的点满足有 np表示的字符的儿子指针
那如果最后跳到了一个 有 np表示字符的 儿子指针 的p 那可怎么办?
(这样的话 那个儿子表示的意思就重复了啊!
- STEP2
设那个已经有的儿子为q 如果q.len是p.len+1
说明这个点表示的字符串可以代替p表示的字符串
把p的link连到q上 (不想直接去重 把q的size++就好了
(其实这个判定只是一个省节点的优化,直接复制不会错)
但如果q.len > p.len + 1的话
就不能代替 而要新建一个点nq
参考博客中给出理由(这里的step就是上文的len)
这和上一种情况一样,也面临着q点是否可以当成x结点的问题.在上一种情况的描述中,我们可以知道, step[q]=step[p]+1可以保证q原本是从p的路径上来的,而且p和q之间不会夹杂其它字符,所以可以直接把q结点当成x结点.那么反过来, step[q]>step[p]+1,就说明p和q之间有可能会夹杂其它字符,这就不能保证把q当成x结点以后,到q的路径都是tx的后缀了,于是我们不能采取和前一种情况一样的做法.但是,我们可以模仿前一种情况的做法.
然后接下来要做的事情相当麻烦。。
要复制到nq上的信息:son; link(nq.link = p);
另外的信息:nq.len = p.len + 1; nq.size = 1; q.link = nq; np.link = nq;
最后把p按link向上跳,把所有的son[np] = q的点的儿子指针都指到nq上
几道题
[AHOI2013]差异
把SAM当图看 题里那个式子就是路径长度嘛。。
Standing Out from the Herd
广义SAM
其实就是加上这个
if(sam[last].ch[c] && sam[last].len + 1 == sam[sam[last].ch[c]].len){
last = sam[last].ch[c];
sam[last].flag = -1;
return ;
}
[NOI2016]优秀的拆分
后缀数组的题 正向维护一个SA 逆向维护一个SA
贴题解
[NOI2015]品酒大会
感觉像SAM裸题
那个inf设得我丧心病狂
也是把SAM当图看
建完sam后DP一下最大值最小值
ans必然是maxmax或minmin