zoukankan      html  css  js  c++  java
  • 后缀自动机&回文自动机学习笔记

    在学了一天其实是边学边摆之后我终于大概$get$后缀自动机了,,,就很感动,于是时隔多年我终于决定再写篇学习笔记辽$QwQ$

    $umm$和$FFT$学习笔记一样,这是一篇单纯的$gql$的知识总结博,对新手并不友好,想学$SAM$的话我是推荐几篇博客:1 2 3(没有$hihocoder$主要我$jio$得有点太理论化了,全是文字没有图其实我挺难看下去的然后也没那么形象比较难理解$kk$

    然后因为我对纯文字的抽象知识点理解起来比较垃圾,,,所以全文可能会放比较多的图$QwQ$

    先放个已经建好的$SAM$介绍下它的基本元素趴$QwQ$

    这个是字符串$aabbabd$的$SAM$.不难发现这个图总的有三个基本元素,分别是节点.绿线.蓝线

    事实上$SAM$的核心也就这三个东西,我大概分别介绍下这三个东西$QwQ$

    首先是节点.

    我们先定义一个东西,叫$endpos(x)$,表示的字符串$x$的所有出现位置,这里的出现位置指的末端的出现位置$QwQ$

    就理解为,$kmp$的时候所有匹配到$x$的末尾节点的位置$QwQ$

    然后对于一个节点的定义就是所有$endpos$相等的$x$的集合.

    拿这个$aabbabd$举例?${aabb,abb,bb}$的$endpos$就都等于${ 4 }$,${b}$的$endpos$就等于${3,4,6}$

    昂然后关于节点几个常见性质记录下,只是证明的话就懒得证了,其实大部分都还挺满显然的?不明白的自己举个例子啥的就$get$了$QwQ$

    1.对于同一个节点内的所有字符串都互为后缀.

    依然拿$aabbabd$举例子?看到这个$aabb,abb,bb$,发现,嗷,确实互为后缀呢$QwQ$

    然后说了没有证明,不过挺显然的我$jio$得其实还,,,

    2.设$x_1$,$x_2$都是$S$的子串,不妨设$|x_1|<|x_2|$,则有.$x_1$是$x_2$的后缀当且仅当$endpos(x_1) supseteq endpos(s_2)$.$x_1$不是$x_2$的后缀当且仅当$endpos(s_1) cap endpos(s_2) = emptyset$

    这个挺好理解我就不举例子辽$QwQ$

    3.设$substrings(st)$表示节点$st$包含的所有字符串,$longest(st)$表示节点$st$所有字符串中最长的一个,$shortest(st)$表示节点$st$所有字符串中最短的一个.则有对所有$xin substrings(st)$,都是$longest(st)$的后缀

    $umm$这个其实和第一个是差不多的我不说了$QwQ$

    4.对于任意一个状态$st$,以及$longest(st)$的任意一个后缀$x$,当$|shortest(st)|leq |x|leq longest(st)$时有$xin substrings(st)$.也就说$substrings(st)$实际上是$longest(st)$的一段连续后缀$w$

    证明显然不明白自己随便举例趴$QwQ$

    5.没了,,,?$QwQ$

    然后是这个绿色的线

    在前面港节点的性质的时候就港了说$substrings(st)$就$longest(st)$的一段连续后缀,所以考虑为什么在$shortest(st)$后面就断开辣?

    就依然拿前面那个${aabb,abb,bb}$港趴,发现继续看后缀应该就是$b$了,但是发现,$b$的$endpos$是${3,4,6}$,也是出现新节点.然后再后面后缀是$\_$,$endpos$是${1,2,3,4,5,6,7}$,于是又出现了新节点

    也就说当$longest(st)$的某个后缀在其他位置出现时,就会断掉,然后出现新的节点

    在这个图上的话是有,节点$S$是$\_$,节点5是$b$,节点4是${aabb,abb,bb}$,于是连一条$4->5->S$的路径,表示的$longest(4)$即$aabb$的所有后缀依次在$4,5,S$中$QwQ$

    对了,叫绿线,听起来就,有点捞,,,还是说下鸭,官方名字叫$Suffix Link$

    最后说下蓝色的线

    首先说另外一个东西,设$next(st)$表示节点$st$遇到的下一个字母的集合,即$next(st)={S[i+1],iin endpos(st)}$

    然后关于这个$next(st)$有一个性质是说,将节点$st$中的所有字符串后面接上字符$c$之后,都依然属于同一个节点

    所以定义一个$trans(st,c)$表示在节点$st$后面接上$c$之后指向的节点.

    然后这个蓝线就是这个$trans$,蓝线上面的字母就$c$.

    最后关于构建我就不说了,,,主要原理我还没有完全$get$(就分类讨论三个里面最复杂的那个,,,我还没$get$昂$kk$

    所以我就不说了,只大概说下,就对每个节点要保存的只有$longset,shortest,suffix link,trans$,然后构造是用的增量法,即将元素一个个加进去$QwQ$

    然后放下板子题代码$QwQ$

    #include<bits/stdc++.h>
    using namespace std;
    #define il inline
    #define ll long long
    #define gc getchar()
    #define mp make_pair
    #define P pair<int,int>
    #define ri register int
    #define rc register char
    #define rb register bool
    #define w(i) edge[i].wei
    #define rp(i,x,y) for(ri i=x;i<=y;++i)
    #define my(i,x,y) for(ri i=x;i>=y;--i)
    #define lb(x) lower_bound(b+1,b+1+num,x)-b
    #define ub(x) upper_bound(b+1,b+1+num,x)-b
    
    const int N=2500000+10;
    int lst=1,tot=1,t[N],A[N],sz[N];
    ll as;
    char str[N];
    struct node{int fa,len,to[30];}nod[N<<1];
    
    il int read()
    {
        rc ch=gc;ri x=0;rb y=1;
        while(ch!='-' && (ch>'9' || ch<'0'))ch=gc;
        if(ch=='-')ch=gc,y=0;
        while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=gc;
        return y?x:-x;
    }
    il void extend(ri c)
    {
        ri p=lst,np=++tot;lst=np;sz[np]=1;nod[np].len=nod[p].len+1;
        while(p && !nod[p].to[c])nod[p].to[c]=np,p=nod[p].fa;
        if(!p)return void(nod[np].fa=1);
        ri q=nod[p].to[c];if(nod[p].len+1==nod[q].len)return void(nod[np].fa=q);
        ri nq=++tot;nod[nq]=nod[q];nod[nq].len=nod[p].len+1;nod[q].fa=nod[np].fa=nq;
        while(p && nod[p].to[c]==q)nod[p].to[c]=nq,p=nod[p].fa;
    }
    
    int main()
    {
        //freopen("3802.in","r",stdin);freopen("3802.out","w",stdout);
        scanf("%s",str+1);ri len=strlen(str+1);rp(i,1,len)extend(str[i]-'a'+1);
        rp(i,1,tot)t[nod[i].len]++;rp(i,1,tot)t[i]+=t[i-1];rp(i,1,tot)A[t[nod[i].len]--]=i;
        my(i,tot,1){ri nw=A[i];sz[nod[nw].fa]+=sz[nw];if(sz[nw]>1)as=max(as,1ll*sz[nw]*nod[nw].len);}
        printf("%lld
    ",as);
        return 0;
    }
    View Code

    阿最后还是写几个基本应用趴$QwQ$

    拓扑序

    根据$len$排序,得到的就是$parent$树的$bfs$序,也是自动机的拓扑序

    rp(i,1,tot)t[nod[i].len]++;rp(i,1,tot)t[i]+=t[i-1];rp(i,1,tot)A[t[nod[i].len]--]=i;
    View Code

    判断子串/后缀

    在$SAM$上匹配,如果没有转移到$NULL$就是子串,如果转移到接受状态就是后缀

    顺便说下匹配?考虑对查询串一个个字母查询,类似于$AC$自动机和$kmp$一样的,一直跳$fa$直到到根或者可以匹配位置.然后如果能匹配就匹配,否则重置$t$和$p$,最后如果匹配完成就加入答案就成$QwQ$

    不同子串个数

    考虑对节点$x$,拥有的子串个数就$len[x]-len[fa[x]]$,然后$sigema$下就成,$over$

    #include<bits/stdc++.h>
    using namespace std;
    #define il inline
    #define ll long long
    #define gc getchar()
    #define mp make_pair
    #define P pair<int,int>
    #define ri register int
    #define rc register char
    #define rb register bool
    #define w(i) edge[i].wei
    #define rp(i,x,y) for(ri i=x;i<=y;++i)
    #define my(i,x,y) for(ri i=x;i>=y;--i)
    #define lb(x) lower_bound(b+1,b+1+num,x)-b
    #define ub(x) upper_bound(b+1,b+1+num,x)-b
    
    const int N=2500000+10;
    int lst=1,tot=1,t[N],A[N],sz[N];
    ll as;
    char str[N];
    struct node{int fa,len,to[30];}nod[N<<1];
    
    il int read()
    {
        rc ch=gc;ri x=0;rb y=1;
        while(ch!='-' && (ch>'9' || ch<'0'))ch=gc;
        if(ch=='-')ch=gc,y=0;
        while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=gc;
        return y?x:-x;
    }
    il void extend(ri c)
    {
        ri p=lst,np=++tot;lst=np;sz[np]=1;nod[np].len=nod[p].len+1;
        while(p && !nod[p].to[c])nod[p].to[c]=np,p=nod[p].fa;
        if(!p)return void(nod[np].fa=1);
        ri q=nod[p].to[c];if(nod[p].len+1==nod[q].len)return void(nod[np].fa=q);
        ri nq=++tot;nod[nq]=nod[q];nod[nq].len=nod[p].len+1;nod[q].fa=nod[np].fa=nq;
        while(p && nod[p].to[c]==q)nod[p].to[c]=nq,p=nod[p].fa;
    }
    
    int main()
    {
        //freopen("3802.in","r",stdin);freopen("3802.out","w",stdout);
        ri len=read();scanf("%s",str+1);rp(i,1,len)extend(str[i]-'a'+1);
        rp(i,1,tot)as+=nod[i].len-nod[nod[i].fa].len;
        printf("%lld
    ",as);
        return 0;
    }
    View Code

    求原串所有子串中字典序第$i$大

    先求出拓扑序,从而求出每个节点的$endpos$数量$QwQ$

    然后就按着拓扑序求出每个节点的子树大小,然后就用逼近法就成$QwQ$

    求两个串的最长公共子串

    先给第一个串建一个$SAM$,然后匹配第二个串就成,$over$

    字符串最小表示法

    考虑先复制字符串并丢进$SAM$中,然后每次贪心走就行$over$

    求两个前缀的最长公共后缀

    $umm$答案就$len[lca(x,y)]$?过分显然了$QwQ$

    (这个可以用在两个串求任意两点结尾的$lcs$,就接在一起中间加"#"就变成这个了$QwQ$

    求任意子串出现个数

    考虑一个子串在它及它的子树中出现?所以按拓扑序$dp$就完事$QwQ$

    求模式串在文本串中的出现位置

    预处理一个$parent$数的倍增和$endpos$大小($umm$这个其实直接$len[x]-len[fa[x]]$就是,,,?

    然后扫描模式串,记录当前匹配到了第多少位,然后判断是否匹配完了.如果匹配完了就倍增往上跳到满足$lengeq$的最高位置,把这个位置的$endpos$集合大小统计到答案中

    最后

    众所周知会$SAM$就会$PAM$了

    所以我顺便写下$PAM$的学习笔记趴$QwQ$

    $umm$其实我$jio$得这和$SAM$没有太大关系,,,?比较像$kmp$昂$QwQ$,,,?

    咕了明天写$QwQ$

  • 相关阅读:
    JAVA面试问题与解答(1-15)
    17个经典的Spring面试问答
    Linux下载——下载文件的命令
    MySQL入门——在Linux下安装和卸载MySQL
    MySQL入门——在Linux下安装和卸载MariaDB
    MySQL入门——MySQL数据库和SQL语言
    Linux性能分析——分析系统性能相关的命令
    Linux网络——查看网络连接情况的命令
    Linux网络——配置网络之iproute家族命令
    Linux网络——配置网络之ifconfig家族命令
  • 原文地址:https://www.cnblogs.com/lqsukida/p/11519978.html
Copyright © 2011-2022 走看看