后缀自动机 模板 【Poi2000】 公共串 bzoj 2946

21 篇文章 0 订阅
7 篇文章 0 订阅

后缀自动机

按照哲学中是什么怎么做为什么的思路,我们来理解一下后缀自动机。

是什么

后缀自动机就是能识别所有的后缀(当然就能顺便识别所有后缀的前缀,就是子串了)的自动机。
它能做什么呢?
它能做字符串问题啊……
比如什么求公共前缀啊,求有多少个本质不同的公共子串,当然它很多时候都和LCT搭配使用,能做的事情就更多。
什么用后缀数组啊,KMP啊啥的能做的题它好像都能做,用后缀树能做的题它也能做!(比如用后缀自动机构建个后缀树啥的……)
反正就是很厉害!

怎么做

假设我们现在已经有一个长度为len-1的后缀自动机了,我们现在要在它后面加一个字符x。
我们设之前最后一次插入的节点为p。
现在就要在p这个节点后新建一个节点np,它的max_len就是整个数组的长度,因为我们要保证这个新插入的节点能表示所有的后缀。
然后我们就判断这个p是否有x这个儿子,如果没有,就把np设成它的x儿子,并且一直跳father(也有很多人喜欢用parent)指针,重复这个过程。
如果跳到了根,那么就说明以前没有出现过x这个字符,就把x的father设成根。
如果突然发现有一个节点既不是根也有x儿子,我们就设这个节点的x儿子为q。
如果发现p的max_ len 恰好是q的 max_len+1,那么就说明q恰好是np的父亲,就直接把np的father设成q就好了。
如果不是的话,我们就再新建一个节点nq,这个nq的所有信息都与q相同,所以直接把q的father和son都赋值给nq。
然后把q的father也设成nq,np的father也设成nq,p的所有x儿子为q的祖先的x儿子都设置成nq。
最后把last指针指向新建的np。
结束了……

我们可以举个例子:%%%PoPoQQQ
(我本来以为我的字是挺丑的,后来照了照片发上来之后才知道,原来我的字是贼JB丑,所以感谢YihAN_Z替我重新画了一张QAQ)
这是后缀自动机的直观形态:
这里写图片描述
(我原来一直不知道为什么当初给我讲后缀自动机的老师死活也不给我们建一个活的后缀自动机看看……自从我自己建了一个PoPoQQQ……我懂了……)
注释:

  • 角标代表的是这个节点的max_len
  • 虚边指向parent树上的父亲节点
  • 实边指向儿子节点
  • 下面的部分为每个节点代表的是哪些串

仔细观察你就可以发现这些节点恰好能识别所有本质不同的子串。
并且也可以感性的理解为什么min_len=fa->max _len+1。

代码还是很好写的,但是理解起来智商吃急啊……
(在这里只描述一下过程,具体的思想还是看陈立杰的论文×∧×)

为什么

为什么要用后缀自动机呢?
因为它快,因为它省空间。
它的常数应该是比后缀数组要小,而且后缀自动机是O(n)的,比后缀数组O(nlogn)要更好。
因为每次我们在插入的时候我们会发现我们最多会插入两个点,所以空间复杂度上界是2n,而且大多数时候还是远达不到的,所以空间复杂度就是O(n)的。

模板代码:

struct SAM{
    SAM *fa,*son[26];
    int max_len;
    SAM(int _=0):fa(0x0),max_len(_)
    {
        memset(son,0,sizeof(son));
    }
}*root=new SAM,*last=root;
void extend(int x)
{
    SAM *p=last;
    SAM *np=new SAM(p->max_len+1);
    while(p && !p->son[x]) p->son[x]=np,p=p->fa;
    if(!p) np->fa=root;
    else
    {
         SAM *q=p->son[x];
         if(p->max_len+1==q->max_len) np->fa=q;
         else
         {
             SAM *nq=new SAM(p->max_len+1);
             nq->fa=q->fa;
             memcpy(nq->son,q->son,sizeof(nq->son));
             q->fa=nq;np->fa=nq;
             for(;p && p->son[x]==q;p=p->fa) p->son[x]=nq;
         }
    }
    last=np;
}

练习题 【poi2000】 公共串 bzoj2946

题目大意:

给出几个字符串,计算最长公共子串的长度。

题目分析:

先用第一个串做后缀自动机。
再用后面几个串在后缀自动机里跑一边,求一下到每个节点为结尾的最长的公共串为多少,最后dfs一遍把答案取一个max就可以了。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 10100
using namespace std;
int n,ans;
char s[N];
struct SAM{
    SAM *fa,*son[26];
    int max_len,min_len[6];
    bool v;
    SAM(int _=0):fa(0x0),max_len(_),v(false)
    {
        memset(son,0,sizeof(son));
        memset(min_len,0,sizeof(min_len));
    }
}*root=new SAM,*last=root;
void extend(int x)
{
    SAM *p=last;
    SAM *np=new SAM(p->max_len+1);
    while(p && !p->son[x]) p->son[x]=np,p=p->fa;
    if(!p) np->fa=root;
    else
    {
         SAM *q=p->son[x];
         if(p->max_len+1==q->max_len) np->fa=q;
         else
         {
             SAM *nq=new SAM(p->max_len+1);
             nq->fa=q->fa;
             memcpy(nq->son,q->son,sizeof(nq->son));
             q->fa=nq;np->fa=nq;
             for(;p && p->son[x]==q;p=p->fa) p->son[x]=nq;
             nq->min_len[1]=nq->max_len;
         }
    }
    last=np;
    np->min_len[1]=np->max_len;
}
void dfs(SAM *p)
{
    int tmp=0x3f3f3f3f;
    for(int i=1;i<=n;i++)
        tmp=min(tmp,p->min_len[i]);
    ans=max(ans,tmp);
    p->v=true;
    for(int i=0;i<26;i++)
        if(p->son[i] && !p->son[i]->v)
            dfs(p->son[i]);
}
int main()
{
    scanf("%d",&n);
    scanf("%s",s);
    for(int i=0;s[i];i++) extend(s[i]-'a');
    for(int i=2;i<=n;i++)
    {
        scanf("%s",s);
        SAM *p=root;int len=0;
        for(int j=0;s[j];j++)
        {
            int c=s[j]-'a';
            while(p!=root && !p->son[c])
                p=p->fa,len=p->max_len;
            if(p->son[c]) p=p->son[c],len++;
            for(SAM *tmp=p;tmp;tmp=tmp->fa)
                tmp->min_len[i]=max(tmp->min_len[i],min(len,tmp->max_len));
        }
    }
    dfs(root);
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值