【后缀自动机】SPOJ(LCS2)[Longest Common Substring II]题解

题目概述

求n个串的最长公共子串。

解题报告

这道题是SPOJ1811的升级版,还是可以用SAM解决。
我们先对第一个字符串构造SAM,然后和SPOJ1811一样用其他串和SAM进行匹配,只不过由于有多个串,我们不能直接刷最大值来求LCS。所以我们将SAM的每个状态都添加一个min记录其他串到此状态时的最小匹配长度(当然无论如何min都不超过该状态的最大子串长度MAX)。那么答案就是所有min中的最大值吗?其实是错的!因为虽然min记录在了某个状态上,但实际上min对一切该状态的祖先都是可行的(只不过匹配的时候匹配了该状态),所以我们还需要向上传递一下,这样才能够刷出正确的答案。
这里说一下如何向上传递,其实并不需要递归处理,只需要按照MAX给所有状态排序,然后由MAX从大到小修正(儿子的MAX一定大于祖先的MAX,这个很好yy,大家可以自己想一下:P),为了不让复杂度多一个log,我们可以使用计数排序。

示例程序

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxl=100000;

int len;
char s[maxl+5];
struct SAM
{
    struct node {int son[26],fa,MAX;};
    node tem[2*maxl+5];int cur,ro,lst;
    int newnode(int M)
    {
        tem[++cur].MAX=M;tem[cur].fa=0;
        memset(tem[cur].son,0,sizeof(tem[cur].son));return cur;
    }
    void clear() {cur=0;ro=newnode(0);lst=ro;}
    void Insert(char ch)
    {
        int ID=ch-'a';int p=lst,np=newnode(tem[p].MAX+1);
        while (p&&!tem[p].son[ID]) tem[p].son[ID]=np,p=tem[p].fa;
        if (!p) tem[np].fa=ro; else
        {
            int q=tem[p].son[ID];
            if (tem[p].MAX+1==tem[q].MAX) tem[np].fa=q; else
            {
                int nq=newnode(tem[p].MAX+1);
                memcpy(tem[nq].son,tem[q].son,sizeof(tem[q].son));
                tem[nq].fa=tem[q].fa;tem[q].fa=tem[np].fa=nq;
                while (p&&tem[p].son[ID]==q) tem[p].son[ID]=nq,p=tem[p].fa;
            }
        }
        lst=np;
    }
    void make_SAM(char *s) {for (int i=1;s[i];i++) Insert(s[i]);}
    int ha[maxl+5],who[2*maxl+5],MIN[2*maxl+5],MAX[2*maxl+5];
    void Sort() //给状态排序
    {
        for (int i=1;i<=cur;i++) MIN[i]=tem[i].MAX; //MIN初始值
        for (int i=1;i<=cur;i++) ha[tem[i].MAX]++;
        for (int i=1;i<=len;i++) ha[i]+=ha[i-1];
        for (int i=1;i<=cur;i++) who[ha[tem[i].MAX]--]=i;
        //计数排序
    }
    void Update(char *s) //将s与此SAM匹配
    {
        int p=ro,len=0;
        for (int i=1;i<=cur;i++) MAX[i]=0;
        for (int i=1;s[i];i++)
        {
            int ID=s[i]-'a';
            if (tem[p].son[ID]) p=tem[p].son[ID],len++; else
            {
                while (p&&!tem[p].son[ID]) p=tem[p].fa;
                if (p) len=tem[p].MAX+1,p=tem[p].son[ID]; else
                p=ro,len=0;
            }
            MAX[p]=max(MAX[p],len); //更新
        }
        for (int i=cur;i>=1;i--) //传递
        {
            int now=who[i];
            MIN[now]=min(MIN[now],MAX[now]);
            int fa=tem[now].fa;
            if (fa) MAX[fa]=min(tem[fa].MAX,max(MAX[fa],MAX[now]));
            //不要忘了和tem[fa].MAX取min,否则会出错!
        }
    }
    int LCS() {int ans=0;for (int i=1;i<=cur;i++) ans=max(ans,MIN[i]);return ans;}
    //众多MIN中取max就是LCS
};
SAM sam;

char readc()
{
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,10000,stdin);
    if (l==r) return EOF; else return *l++;
}
int reads(char *s)
{
    int len=0;char ch=readc();if (ch==EOF) return EOF;
    while ('z'<ch||ch<'a') ch=readc();s[++len]=ch;
    while ('a'<=s[len]&&s[len]<='z') s[++len]=readc();s[len--]=0;
    return len;
}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    len=reads(s);sam.clear();sam.make_SAM(s);sam.Sort();
    while (~reads(s)) sam.Update(s);
    printf("%d\n",sam.LCS());
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值