zoukankan      html  css  js  c++  java
  • 「后缀自动机」学习笔记

    定义

    一个字符串S对应的后缀自动机(SAM)是一个最小的确定有限状态自动机(DFA),接受且只接受S的后缀。可以理解为能够在SAM上找到该串的所有子串,且使得SAM状态数最少。

    状态

    $endpos$集

    对于S的一个子串s',endpos(s') 为S中所有s'的结束位置集合。以S="aabbabd"为例,endpos("ab") = {3,6}

    $endpos$等价类

    如果两个子串的endpos集相等,就把这两个子串归为一类。称所有endpos集相同的子串为一个endpos等价类。定义一个endpos等价类作为SAM的一个状态。一个endpos等价类中的串互为后缀且长度连续。可以理解为一段后缀。

    后缀链接

    定义一个状态(也就是一个endpos等价类)中最长串s1长度为maxlen(简称len),最小串s2长度为minlen。s2长度不一定为1,因为s2的后缀的endpos集可能不同于s2本身的endpos集。同时,后者一定属于前者(仔细思考)。于是我们考虑在这两个状态之间建立一种联系,称之为后缀链接(Suffix Link)。从一个状态出发不停跳后缀链接,相当于不停跳到自己的后缀,最终会跳到初始状态(空)。我们称这条路径为后缀路径(Suffix Path)。

    状态转移

    注意后缀链接不等同于状态转移,前者不是一个自动机必须具备的,而后者是。

    考虑一个状态u,如果其中所有串的末尾都加上一个相同字符c,那么应该对应哪个状态?这些原本的串加上一个相同字符之后,应当全部同时存在于一个新的状态v中。因此一个状态能通过一个字符转移到另一个状态。记为trans[u][c]

    构造后缀自动机

    增量法,即考虑已经构建好字符串S(设长度为n-1)的SAM,现在要在S后面加上字符c。也就是说,SAM要新增去识别以这个新增的c为结尾的后缀了。

    加入c后,后缀自动机的构造会发生变化。同时endpos发生变化的一定是新串的后缀

    由于新增了一个位置,肯定会多一个endpos集{n},因此新开一个状态z。

    情况一:从las开始一路跳后缀链接,一直发现trans[p][c]不存在。

    这个情况非常特殊。等价于c是S中没有出现过的。因此所有后缀的endpos一定都是{n}。一路上的点都连z即可。z状态包括了以n结尾的所有后缀,因此后缀链接为源点。

    情况二:后缀链接的路上点有存在trans[p][c]!=null的,len(p)+1=len(q)

    也就是当前后缀在原串中不仅仅出现n那里一次。设trans[p][c]=q,我们判断q的len是多少。如果len(p)+1=len(q),它的意义就是q中的串全都是p中的串+c得到的。因此对应的后缀全都在q里。因此直接将z的后缀链接设为q即可。此时已经找到了不能表示的最长后缀,直接跳出。

    情况三:后缀链接的路上点有存在trans[p][c]!=null的,len(p)+1<len(q)

    有一部分后缀与当前一样,但一部分后缀的前面部分并不一样。也就是说加上c以后,原本q的endpos集一个会多出{n},一个不变。因此就需要把q拆开了。新建一个状态nq。而这两个集后面再加一个字符,endpos肯定又一样了(新后缀再加一个字符,没这个玩意儿,又回来了)。因此他们的出边都是q原来的出边。考虑后缀链接。现在有q,nq,fa(q),他们互为后缀关系,又显然存在len(q)>len(nq)>len(fa(q))。最后z的fa了,显然是nq。然后再走回去,路上如果存在连着q的,帮他改成nq就行了。这里和情况二是一个道理,一旦不等于q了,就可以结束了。

    挺难理解的,自己也没理解透。

    /*DennyQi 2019*/
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <queue>
    using namespace std;
    const int N = 2000010;
    inline int read(){
        int x(0),w(1); char c = getchar();
        while(c^'-' && (c<'0' || c>'9')) c = getchar();
        if(c=='-') w = -1, c = getchar();
        while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); 
        return x*w;
    }
    char s[N];
    int n,las=1,cnt=1,ans,cnte,nl,fa[N],son[N][26],len[N],sz[N],head[N],nxt[N<<1],to[N<<1];
    inline void SAM_add(int c){
        int p = las;
        sz[las = ++cnt] = 1;
        len[las] = nl;
        for(; p && !son[p][c]; p = fa[p]) son[p][c] = las;
        if(!p){ fa[las] = 1; return; }
        int q = son[p][c];
        if(len[p]+1 == len[q]){ fa[las] = q; return; }
        len[++cnt] = len[p]+1;
        memcpy(son[q],son[cnt],sizeof(son[q]));
        fa[cnt] = fa[q], fa[q] = fa[las] = cnt;
        for(; son[p][c]==q; p = fa[p]) son[p][c] = cnt;
    }
    inline void Tree_add(int u, int v){
        to[++cnte] = v;
        nxt[cnte] = head[u];
        head[u] = cnte;
    }
    void dfs(int u, int Fa){
        for(int i = head[u]; i; i = nxt[i]){
            dfs(to[i],u);
            sz[u] += sz[to[i]];
            if(sz[u] != 1) ans = max(ans,sz[u]*len[u]);
        }
    }
    int main(){
        // freopen("file.in","r",stdin);
        scanf("%s",s+1);
        n = strlen(s+1);
        for(nl = 1; nl <= n; ++nl) SAM_add(s[nl]-'a');
        for(int i = 2; i <= cnt; ++i) Tree_add(fa[i],i);
        dfs(1,-1);
        printf("%d",ans);
        return 0;
    }

    1. 最长公共子串 

    第二个串直接在第一个串的SAM上走。失配时跳fa,因为既然失配,那么当前这个endpos肯定没用了,跳到最长的后缀继续匹配。思想和KMP是一样的。

    2. 多串最长公共子串

    一个一个在第一个串的SAM上走。记录对于每一个结束位置能匹配的最大长度,最后每个位置取min,所有位置取max。值得注意的是一个节点满足时,所有祖先节点都要满足,而且不能超过len。

    3. 最小表示法问题

    复制一遍串接在后面,然后再SAM上贪心就可以了。类似之前01trie树的做法。

    一个难点是需要用一个map来存son。用map有一个好处是memcpy可以不需要,map支持直接复制。son[cnt]=son[q]

    后缀自动机好麻烦啊(我好菜啊),还是后缀数组吧QAQ

  • 相关阅读:
    Educational Codeforces Round 86 (Rated for Div. 2) D. Multiple Testcases
    Educational Codeforces Round 86 (Rated for Div. 2) C. Yet Another Counting Problem
    HDU
    HDU
    HDU
    HDU
    Good Bye 2019 C. Make Good (异或的使用)
    Educational Codeforces Round 78 (Rated for Div. 2) C. Berry Jam
    codeforces 909C. Python Indentation
    codeforces1054 C. Candies Distribution
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/11046028.html
Copyright © 2011-2022 走看看