zoukankan      html  css  js  c++  java
  • 【算法专题】后缀自动机SAM

    后缀自动机是用于识别子串的自动机

    学习推荐:陈立杰讲稿,本文记录重点部分和感性理解(论文语言比较严格)。

    刷题推荐:[后缀自动机初探],题目都来自BZOJ。

    【Right集合】

    后缀自动机真正优于后缀树的方面在于:结合了有限状态自动机,从而实现了O(n)的时空复杂度。

    trans(s,str)表示s+str到达的状态。

    ST(str)=trans(init,str)即包括了str这一子串的唯一状态(一个子串只能属于一个状态)

    定义字符串a在S中出现的右端点位置集合Right(a)=r1,r2...rn

    SAM中一个状态s的Right集合为Right(s),那么状态s表示所有[Right(a)=Right(s)的子串a],那么实际上状态s表示的子串a的长度在一个区间内,记作[Min(s),Max(s)]。字符串越短,Right集合越大。

    容易证明对于任意状态a和b,Right(a)和Right(b)要么包含要么不相交。(很显然的结论,只是论文的证明比较严格化)

    ★在后缀自动机中,一个状态s(或称“节点s”)表示的是Right(a)=Right(s)的若干子串a,其长度区间为[MIn(s),Max(s)],从而达到状态数O(n)的目的。

    Right集合形成的树形包含关系称为Parent Tree,树边由孩子指向父亲,是SAM中的失配边。

    Parent树从上往下,子串长度扩大,Right集合变小,父节点的Right集合包含子节点的Right集合

    fa=Parent(s)当且仅当fa是使Right(fa)最小且满足Right(s)⊂Right(fa)的结点,(从儿子到父亲,就是子串稍微缩短,Right集合变大)

    另有Max(fa)=Min(s)-1,实质上是要求fa和s之间没有一个Right子集x满足Right(s)⊊Right(x)⊊Right(fa)。

    【线性构造SAM】

    线性构造采用增量法,即已知字符串T的SAM(T),L=Len(T),在末尾加入字符x,构造SAM(Tx),转移如下:

    ①实边转移规则:t=trans(s,x)表示s--->t标号为x的边,若Right(s)={r1,r2...rn},则Right(t)={ri+1|s[ri+1]=x}

    ②虚边转移规则:Parent树边满足Right(s)⊊Right(fa),Max(fa)=Min(s)-1(含义如上所述)

    加入x,考虑所有Right集合包含L的结点v1,v2...vk,显然它们在Parent树上是一条从根到叶子的链。

    定义叶子结点v1=p=ST(T)(即p代表整个串),Right(p)={L},不妨它们从后代到祖先排为v1=p...vk=root。

    同时新建结点np=ST(Tx),Right(np)={L+1}。

    考虑节点v,Right(v)={r1,r2...rn=L}

    根据转移规则①,如果除了rn外不存在S[ri+1]=x,那么节点v不存在trans(v,x)。

    根据转移规则②,越往上Right集合逐渐扩大直至节点vp存在trans(vp,x),那么v1~vp-1只须向np连出标号为x的边(np=trans(v,x)),而vp~vk都已经存在trans(v,x)。

    令trans(vp,x)=q,当Max(q)=Max(vp)+1时,只须在q的Right集合中插入L+1。

    下面重点讨论Max(q)>Max(vp)+1的情况。

    eg.

    T+x:aaabcaaabcaa+b

    vp:aaabcaaabcaa  Max(vp)=2

    q:aaabcaaabcaa   Max(q)=4

    根据实边的转移规则,实际上节点q不止由vp转移过来,也由vp在Parent树上的儿子o转移过来(多条实边连入):

    o:aaabcaaabcaa  Max(o)=3

    可见,o才满足Max(q)=Max(o)+1。trans(vp,b)形成了aab,而trans(o,b)形成了aaab,由于有限状态自动机的性质Right(aab)=Right(aaab)所以合并成同一个点p。

    但是当末尾+b时,我们发现Right(aab)>Right(aaab)也就是出现了新的Right集合aab——Right(aab)=Right(q)+Right(np),所以创造新的节点nq:

    nq:aaabcaaabcaab  Max(nq)=3

    Right(nq)实际上是Right(q)和Right(parent(q))夹出来的新Right集合(aab之前被并入q现在分离出来),并且vp将改为连向nq。

    vp的祖先中连向q的部分要改为连向nq,因为这些点连向q说明+x后Right集合=Right(q),那么现在就要连向nq。

    线性构造SAM的具体步骤如下:

    1.新建np,找到vp,vp之前的点连向np。若不存在vp则fa(np)=-1。

    2.若len(q)=len(vp)+1,fa(np)=q,结束。

    3.len(q)>len(vp)+1,新建节点nq复制q信息并修改len。

    4.从vp到根若连向q改为nq。

    void insert_SAM(int c){
        int np=++size;
        t[np].len=t[last].len+1;
        int x=last;
        last=np;
        while(x&&!t[x].t[c])t[x].t[c]=np,x=t[x].fa;
        if(!x)t[np].fa=root;else{
            int y=t[x].t[c];
            if(t[y].len==t[x].len+1)t[np].fa=y;else{
                int nq=++size;
                t[nq]=t[y];//
                t[nq].len=t[x].len+1;
                t[nq].fa=t[y].fa;t[y].fa=t[np].fa=nq;
                while(x&&t[x].t[c]==y)t[x].t[c]=nq,x=t[x].fa;//
            }
        }
    }
    
    last=size=root=1;
    for(int i=1;i<=m;i++)insert_SAM(s[i]-'a');
    View Code

    注意:后缀自动机节点数是2n。

    【技巧和应用】

    后缀自动机没有高论,本质上仅仅是识别子串的自动机,为了压缩时空复杂度将Right集合相同的子串集合作为一个节点,然后构造自动机的一般属性:trans边(实边)和fail边(虚边)。

    1.节点的本质:每个节点是Right集合,是Right集合相同的子串集合。

    2.实边:后接字符。

    从x的某个子串后面+一个字母,能够得到y的某个子串。(所以从根沿子串走能得到子串所在节点)

    3.虚边:前删字符。(匹配失败fail)

    删除最少的字符使得Right集合变化。故该串在Parent树上的祖先都是该串的后缀。

    1.Right集合:SAM中每个节点是right集合。

    每次的np称为“关键节点”,这个节点的最长字符串是以该Right点结尾的前缀。(它最终不一定是叶子)。

    Right集合大小:np值为1,按Parent树建边进行dfs统计即可。Right集合大小就是每个状态代表串的出现次数

    例题:【BZOJ】3998: [TJOI2015]弦论

    统计Right集合具体的话可以用线段树合并或set启发式合并。

    实边:每个节点x连出实边y相当于在串s(x)后+x到达另一个节点(right尽可能大),这个到达的节点串的前面可能会延伸。

    每一条从根到节点的路径都是一个子串对应的状态。虚边:连接右端点位置相同而长度不同子串,该串在Parent树上的祖先都是该串的后缀,这就可以用来作为fail树。)

    4.匹配:一个串从自动机根节点往下沿实边走并cnt++,不能走就沿虚边fail至能继续走的点y(cnt=len(y)+1),然后往下走。因为Parent的父亲一定是后缀,这样能识别出这个串和SAM的以每个位置结尾的最长公共子串的节点。这些识别点在Parent树上的所有祖先节点合起来就是所有公共子串。

    例题:【BZOJ】4032: [HEOI2015]最短不公共子串(LibreOJ #2123)

    5.后缀树:对逆序串建立SAM,parent树就是原串的后缀树,后缀的LCP就是后缀树上两个np的LCA。

    例题:【BZOJ】3238: [Ahoi2013]差异

    6.找到指定串节点:在Parent上从对应右端点位置的“前缀开端节点”倍增到Len合适的位置。

    例题:【BZOJ】3676: [Apio2014]回文串

    7..子串数:对每个点x,Max(x)-Max(fa(x))得到的就是该点的长度区间即子串数,所有点的子串数就是子串总数。

  • 相关阅读:
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之六 多点触控
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之九 定位
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之七 重力感应
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之五 保存数据的几种方式
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之八 照相机
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之三 Application 配置详解
    Adobe Flash Builder 4.5 Android Air 程序开发系列 之四 打开与关闭应用程序是的保存数据
    ADOBE FLASH BUILDER 4.6 IOS 开发之部署与调试
    [译] 高性能JavaScript 1至5章总结
    页签及盒子的web标准实现
  • 原文地址:https://www.cnblogs.com/onioncyc/p/8116235.html
Copyright © 2011-2022 走看看