「专题总结」后缀自动机

后缀自动机重点在于性质,东西很多注意区分概念。

  1. 后缀自动机是一个\(DAG\),从根开始的路径能够识别\(S\)的每个后缀(子串),一定不存在一条从根开始的路径能够识别不是S的子串。

  2. 点:每个节点代表了一个\(endpos\)类,从根到该节点的所有字符串在S中的出现位置相同,
    一个点代表的\(endpos\)集相同的各个串之间有后缀关系且连续,暂且称这些串的集合为\(P\)

  3. 边:走\(trans\)相当于在后边加字符,跳\(parent\)树则是在前面减字符。

  4. \(nq\)节点是为了划分不同的\(endpos\)集合,有自己的\(len\),但不是\(S\)的某个前缀,不具有\(siz\)

  5. 一个节点的\(endpos\)大小是\(parent\)树上的实点个数。

  6. 反串的\(parent\)树上,两点的\(lcp\)\(lca\)\(len\),注意x y为lca。

  7. 广义后缀自动机能够识别多个串的子串。

  8. \(minlen(x)=maxlen(fa[x])+1\),所以按\(len\)排序(桶排),可以得到\(trans\)拓扑序。

  9. 称一个节点\(x\)能够代表的字符串为根到x的所有路径(\(trans\))上的字符排列,则每个节点代表的字符串不充不漏,本质不同。


应用:

  1. 求本质不同子串,统计根到其他点的路径数,或\(Ans=\sum\limits_{i=2}^{tot}len[i]-len[fa[i]]\)
  2. 求所有后缀的lcp,建反串的SAM,那么两个前缀实点的lcp是parent树上的lca的len,
    由于是前缀不需要和\(length\)取min,拓扑统计过每个点(作为lca)的实点点对数。《差异》

  3. 字典序最小循环串,复制一倍,建SAM,贪心地从'a'->'z'走\(|S|\)长。《工艺》

  4. 每次加入字符,在线求本质不同子串。发现答案的增量只有\(len[np]-len[fa[np]]\),其他点内部守恒。《生成魔咒》

  5. 求S和T的最长公共子串,在S的后缀自动机上跑T匹配,若失配则暴力跳fa(由于匹配一位在parent tree上深度至多+1
    ,跳fa深度一定-1,所以类似栈的势能复杂度分析, 为\(O(lenth_T)\)),每次得到以i结尾的和S匹配的最长后缀长度\(mx_i\),那么\(Ans=max(mx_i)\)

  6. 求多个串的最长公共子串,对\(S_1\)建SAM,同上对其余串在SAM上跑匹配,记录匹配\(mx\)信息在SAM的节点上,
    表示该串在这个endpos相同的后缀串集合\(P\)中最多匹配到哪个长度,由于parent上的祖先有该后缀集合中所有串的后缀,
    一个节点只要有mx值,就要把其parent树祖先\(p\)全部置为\(len_p\)。最后答案为在每个SAM节点上(所有串mx值最小值)的最大值。《公共串》

  7. 求第K小子串:在DAG上dp出每个节点开始有多少条路径,从根开始走相当于不断缩小问题规模,
    如果\(k>dp[v]\)则跳过,否则走一步并减去答案串到点v的路径数。《弦论》
    还是想说一下这个DP,如果要求本质不同,当\(rd[u]=0\)时就要++\(dp[u]\),表示无论这个点(这些串)的\(|endpos|\)多大,
    我也只算一次,然后“带着”这个出现的贡献一直dp到根统计路径。
    如果可以重复,那么就要有\(du[u]\)+=\(|endpos_u|\),位置要算不同,实际上是在根到u的路径数上乘了\(|endpos_u|\)
    同理有\(\sum\limits_{i=2}^{tot}(len[i]-len[fa[i]]) \times |endpos_i|=\frac {n(n+1)}{2}\)

  8. T在S中出现次数,在S的SAM上匹配T,如果失配说明S中不存在完整T,否则能找到在SAM上表示T的集合\(P_u\),T的出现次数就为\(|endpos_u|\)
    如果要支持在线带修,需要打棵LCT维护动态parent树。维护子树和或者链加单点查都行。
    注意第二种要给nq点赋同q点值《Substring》


下面是一些不很模板的应用:

B. 诸神眷顾的幻想乡(其实是广义模板)

求树上本质不同子串。叶子节点不超过20个,于是可以以每个叶子为根建广义后缀自动机,再加上Deepinc的板子
本题给出了Trie,固定一个根rt,实际上一次dfs建出的SAM,和枚举叶子节点建出的SAM形态相同。因为q=las的特判。
在线建的复杂度为\(\Theta(|A||T|+G(T))\)\(|A|\)为字符集大小,\(|T|\)为把所有串插入Trie的大小,\(G(T)\)是Trie上所有叶子深度之和。
DFS在线会被卡成\(n^2\)?不会证。。。

H. Cheat

答案具有单调性,考虑二分L。
对M个串建广义SAM,处理出l[i]为以i结尾的最长匹配后缀长度。不能匹配到L就断开,因为之后的部分不到L就亏了。
区间划分问题,考虑DP。
\(f[i]\)为i之前的最大熟悉长度,有转移\(f[i]=max(f[i-1],f[j]+i-j),i-l[i] \leq j \leq i-L\)
因为\(l[i] \geq l[i+1]\),所以\(i-l[i] \leq i+1-l[i+1]\)
决策具有单调性,用单调队列优化下就可以\(\Theta(n)\)check了。

SAM模板

void extend(int c){
    int p=las,np;np=las=++tot;
    len[np]=len[p]+1;
    for(;p&&!to[p][c];p=fa[p])to[p][c]=np;
    if(!p)fa[np]=1;
    else{
        int q=to[p][c];
        if(len[q]==len[p]+1)fa[np]=q;
        else{
            int nq=++tot;len[nq]=len[p]+1;
            F(i,0,25)to[nq][i]=to[q][i];
            fa[nq]=fa[q];fa[np]=fa[q]=nq;
            for(;p&&to[p][c]==q;p=fa[p])to[p][c]=nq;
        }
    }
}
   
广义SAM模板

void extend(int x){
    int p=las,q,nq,np;
    if(q=to[p][x]){
        if(len[q]==len[p]+1){las=q;return;}
        las=nq=++tot;len[nq]=len[p]+1;
        F(i,0,c)to[nq][i]=to[q][i];
        fa[nq]=fa[q];fa[q]=nq;
        for(;p&&to[p][x]==q;p=fa[p])to[p][x]=nq;
    }
    else{
        np=las=++tot;
        len[np]=len[p]+1;
        for(;p&&!to[p][x];p=fa[p])to[p][x]=np;
        if(!p)fa[np]=1;
        else{
            q=to[p][x];
            if(len[q]==len[p]+1)fa[np]=q;
            else{
                nq=++tot;
                len[nq]=len[p]+1;
                F(i,0,c)to[nq][i]=to[q][i];
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                for(;p&&to[p][x]==q;p=fa[p])to[p][x]=nq;
            }
        }
    }
}
   

猜你喜欢

转载自www.cnblogs.com/hzoi-yzh/p/12115758.html