SAM学习笔记

版权声明:菜逼一只 https://blog.csdn.net/Rose_max/article/details/79770803

主要参考的是这篇blog
以及这篇俄文翻译
upd:一些性质参考的是这篇blog,Menci超劲啊!
因为clj的论文太难懂了其他blog又是特别抽象的对我这种蒟蒻特别不友好
这篇blog是方便自己复习写的所以难免有引用其他blog的地方

一些定义

定义ch[p][s]表示在状态p上增加一个字母s后能转移到的状态,如果不能转移则为0
定义str(s)表示在原串中走到s这个位置所构成的字符串
定义right(i)表示str(i)在原串中出现过的右端点的集合
定义min(i)表示root->i能走出的最短子串长度 max(i)表示能走出的最长子串长度
定义parent(i)表示在SAM中,right(i)是right(parent(i))的真子集且right(parent(i))最小
可以发现所有的parent连起来可以构成一棵树,将这棵树称为parent树

parent树的一些性质

1:从叶子节点向上走就是right集合不断合并的过程
2:设一个状态最短串为mins,最长串为maxs=lens,那么mins-1=max_fa[s]
3:网上说的接受后缀,其实可以换一种好理解的说法,parent的树从下到上right集合不断变大其实就是不断寻找后缀的过程

构造方法

增量法
对于已经建立好SAM的串1~last,设插入后的状态为np,插入字母为c
那么max(p)=max(last)+1,将ch[last][c]=np
然后我们开始跳last,设这个跳动的节点为p
如果ch[p][c]=-1的情况时,由于p的right集合是包含了last的right集合的,那么可以直接将ch[p][c]指向p
当ch[p][c]!=-1的时候,可以发现当前已经有这个状态了,设ch[p][c]=q
分类讨论:
1:当max[p]+1=max[q]时,我们可以知道,q这个状态是从p直接转移过来的。由于p的right集合包含了last的right集合,q的right集合也可以包含了np的right集合,那么直接将np的parent指向q然后退出
2:当不等于时,我们就要新建立一个万能的节点nq,使max[nq]=max[p]+1,然后将q和np的parent指向nq。然后对于前面状态与q合并了的,将他的孩子指向nq
代码中dep代替了max的作用

void add(int k)
{
    int x=a[k];
    int p=last,np=++cnt;
    tr[np].dep=k;
    while(p && tr[p].son[x]==0)tr[p].son[x]=np,p=tr[p].parent;
    if(p==0)tr[np].parent=root;
    else
    {
         int q=tr[p].son[x];
         if(tr[q].dep==tr[p].dep+1)tr[np].parent=q;
         else
         {
            int nq=++cnt;
            tr[nq]=tr[q];tr[nq].dep=tr[p].dep+1;
            tr[q].parent=tr[np].parent=nq;
            while(p && tr[p].son[x]==q)tr[p].son[x]=nq,p=tr[p].parent;
         }
    }
    last=np;
}

证明

主要证明一下构造中max(q)!=max(p)+1的情况
可以发现这时候q不是从p这里拓展出来的了,但是对于某些q的后缀,他们也是可以接受np的
这些后缀的max长度为max(p)+1,那么从q中脱离出来一些状态,设这个状态为nq
将max(nq)设为max(p)+1,其余与q不变。由于nq是q中脱离出来的,他的right集合肯定包含了q的right集合,于是可以将q的parent指向nq。
这时候nq已经脱离完毕了,将np的parent也指向nq。对于之前一些与q合并了的状态,我们也要将他指向nq啦

应用

可以参考一下那篇俄文翻译上的内容
或者接下来的SAM题表

广义后缀自动机

其实就是对一个Tire建后缀自动机
Tire上的后缀的定义为:一个Tire节点走到根的路径构成的子串
那么这个后缀自动机的目标就是能识别这堆子串
于是你就BFS建图,搜到一个节点的时候把last设为这个节点的父亲,然后跟狭义后缀自动机一样讨论建就OK了

展开阅读全文

没有更多推荐了,返回首页