SPOJ 1812 Longest Common Substring II 后缀自动机求多字符串最长公共子串

题意:

给若干字符串,求它们的最长公共子串的长度。

题解:后缀自动机。

对第一个串建立SAM,并拓扑排序。
用后面的串分别匹配。
对于SAM,每个节点新增两个值ml,ans;

ml代表该节点满足单一字符串时的最大值,匹配完一个字符串后重置为0;

ans代表该节点满足所有字符串的最大值,初始化为该状态建立时的代表的子串的长度的最大值(max),每次匹配后更新为min(ans,ml)。

注意每次匹配完字符串,按照拓扑排序从后往前更新(保证父节点在子节点后被更新)用子节点的ml更新父节点的ml,证明如下:

根据后缀自动机上min[i]=max[fa[i]]+1,子节点能匹配的长度ml比父节点的max更长,但父节点是子节点的后缀,父节点也能匹配子节点匹配的子串的后max位,但实际在自动机上该匹配子串并没有转移到父节点过,其ml=0,或其ml比此次能匹配的max小,所以一旦子节点的ml不为0,父节点的ml定可修改为父节点的max值。

最后求所有节点ans的最大值即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std;

const int N=200005;
int ans;
char s[N];
struct sam
{
    int cnt,last;
    int to[N][26],fa[N],mx[N],ml[N],ans[N];
    int c[N],q[N];

    sam()
    {
        last=++cnt;
    }

    void extend(int c)
    {
        int p=last,np=last=++cnt;
        mx[np]=mx[p]+1;ans[np]=mx[np];
        while(p&&!to[p][c])to[p][c]=np,p=fa[p];
        if(!p)fa[np]=1;
        else 
        {
            int q=to[p][c];
            if(mx[q]==mx[p]+1)fa[np]=q;
            else 
            {
                int nq=++cnt;
                mx[nq]=mx[p]+1;ans[nq]=mx[nq];
                memcpy(to[nq],to[q],sizeof(to[q]));
                fa[nq]=fa[q];
                fa[q]=fa[np]=nq;
                while(to[p][c]==q)to[p][c]=nq,p=fa[p];
            }
        }
    }

    void build()
    {
        scanf("%s",s+1);
        int n=strlen(s+1);
        for(int i=1;i<=n;i++)
            extend(s[i]-'a');
        for(int i=1;i<=cnt;i++)c[mx[i]]++;
        for(int i=1;i<=n;i++)c[i]+=c[i-1];
        for(int i=cnt;i>=1;i--)q[c[mx[i]]--]=i;
    }

    void update()
    {
        int n=strlen(s+1);
        int nowlen=0,p=1;
        for(int i=1;i<=n;i++)
        {
            int c=s[i]-'a';
            if(to[p][c])
                nowlen++,p=to[p][c];
            else
            {
                while(p&&!to[p][c])p=fa[p];
                if(p)nowlen=mx[p]+1,p=to[p][c];
                else nowlen=0,p=1;
            }
            ml[p]=max(ml[p],nowlen);
        }
        for(int i=cnt;i>=1;i--)
        {
            int p=q[i];
            ans[p]=min(ans[p],ml[p]);
            if(fa[p]&&ml[p])ml[fa[p]]=mx[fa[p]];
            ml[p]=0;
        }
    }
}sam;

int main()
{
    //freopen("lx.in","r",stdin);
    sam.build();
    while(scanf("%s",s+1)!=EOF)
        sam.update();
    for(int i=1;i<=sam.cnt;i++)
        ans=max(ans,sam.ans[i]);
    cout<<ans;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值