SPOJ LCS2 Longest Common Substring II

SPOJ LCS2 Longest Common Substring II

后缀自动机

题意

给出若干串,求所有串的最长公共子串。

思路

SAM。
对于第一个串建SAM,用后面的串在上面跑。开个数组统计每个状态的最大长度,每个字串搞完了后合并到一个新数组上。合并每个字串的长度时各字串之间取min。最后统计每个状态的max即可。

然后WA。。。因为每个可匹配的状态x的前驱状态y也是可以匹配的。。但是在自动机上跑的过程可能没跑到y这个状态。。。另一个字串只能匹配y状态,造成最后合并时认为xy都不能匹配。。

如何处理前驱呢?思路是在失配树(suffix link tree)上dfs。不过我们不用DFS,排序即可。因为子状态的maxlen一定大于父状态的maxlen。只要排过序,从maxlen大的开始遍历,就可以O(n)更新。

不过排序会退化成nlogn,为了避免,我们用基数排序,因为maxlen不超过字串长度n。基数排序的ji数组相当于统计每个数有多少个,id是新标号。

我因为基数排序排错了而gg好几发。。

%%%

有多个字符串(<=10)求LCS
首先对首个字符串建立后缀自动机,紧接着对每个字符串,都进行匹配,x数组处理在此之前的字符串匹配到这一个状态的LCS(取前面所有y[i]的min),y数组表示在当前字符串中匹配到这一位的最大lcs。然后每做完一个字符串就要把x数组更新。答案就是max(x[i])其中我们发现每次更新x数组的时候,要把这个状态的所有pre值都处理,为了保证线性维护,我们要先预处理出一个拓扑序列,倒着处理就可以了。处理出拓扑序列的方法不需要用常规的bfs或dfs,会超时,只要用一个类似桶排的方法按照每一个状态距离根的最长距离(a[p].step)来排就可以处理出一个拓扑序列了。

话说我做这道题真是曲曲折折。第一次想到了大概做法,写完之后random对拍过,然后wa第10个(估计手出)的测试点。紧接着发现自己把x和y的数组一起处理没有考虑先后,然后改了之后还是wa第10个点,又发现每个状态的pre没有处理到。又写了dfs的拓扑发现超时,改成线性的居然还是超时。郁闷之下提交大神的代码居然也还是超时,整整耗了半个小时重新提交就AC了。论SPOJ的坑爹性,你换台好一点的评测机又不会怀孕。。。

代码

#include<bits/stdc++.h>
#define M(a,b) memset(a,b,sizeof(a))
typedef long long LL;
using namespace std;
const int MAXL=500005;
const int MAXS=26;

struct SAM
{
    int amo=0, len, st;
    int maxlen[2*MAXL+10], trans[2*MAXL+10][MAXS], slink[2*MAXL+10], endposamu[2*MAXL+10];
    int ma[MAXL*2+10], mi[MAXL*2+10];

    int new_state(int _maxlen, int _minlen, int* _trans, int _slink)
    {
        amo++;
        maxlen[amo]=_maxlen;
        for(int i=0; i<MAXS; i++)
        {
            if(_trans==NULL)
                trans[amo][i]=0;
            else
                trans[amo][i]=_trans[i];
        }
        slink[amo]=_slink;
        return amo;
    }

    int add_char(char ch, int u)
    {
        int c=ch-'a';
        int z=new_state(maxlen[u]+1, -1, NULL, 0);
        int v=u;
        while(v!=0&&trans[v][c]==0)
        {
            trans[v][c]=z;
            v=slink[v];
        }
        if(v==0)
        {
            slink[z]=1;
            return z;
        }
        int x=trans[v][c];
        if(maxlen[v]+1==maxlen[x])
        {
            slink[z]=x;
            return z;
        }
        int y=new_state(maxlen[v]+1, -1, trans[x], slink[x]);

        slink[x]=y;
        slink[z]=y;
        int w=v;
        while(w!=0&&trans[w][c]==x)
        {
            trans[w][c]=y;
            w=slink[w];
        }
        return z;
    }
    void init()
    {
        memset(maxlen, 0, sizeof(maxlen));
        memset(trans, 0, sizeof(maxlen));
        memset(slink, 0, sizeof(maxlen));
        memset(endposamu, 0, sizeof(endposamu));
        amo=0;
        st=new_state(0, -1, NULL, 0);
        memset(mi, 0x3f, sizeof(mi));
        memset(ji, 0, sizeof(ji));
        memset(id, 0, sizeof(id));
    }
    void addstring(char s[], int n)
    {
        len=n;
        int la=st;
        for(int i=1;i<=n;i++)
        {
            la=add_char(s[i], la);
        }
        prepare();
    }
    int ji[MAXL+5], id[2*MAXL+7];
    void prepare()
    {
        ji[0]=0;
        for(int i=st;i<=amo;i++) ji[maxlen[i]]++;
        for(int i=1;i<=amo;i++) ji[i]+=ji[i-1];
        for(int i=st;i<=amo;i++) id[ji[maxlen[i]]--]=i;
    }
    void match(char t[], int m)
    {
        memset(ma, 0, sizeof(ma));
        int k=st;
        int len=0;
        for(int i=1;i<=m;i++)
        {
            int now=t[i]-'a';
            if(trans[k][now])
            {
                k=trans[k][now];
                len=len+1;
            }
            else
            {
                while(!trans[k][now]&&k)
                {
                    k=slink[k];
                    len=maxlen[k];
                }
                if(!k) k=st, len=0;
                else len=maxlen[k], k=trans[k][now], len++;
            }
            ma[k]=max(ma[k], len);
        }
        for(int i=amo;i>=st;i--)
        {
            int now=id[i];
            mi[now]=min(mi[now], ma[now]);
            if(ma[now]&&slink[now]) ma[slink[now]]=maxlen[slink[now]];
        }
    }
}sam;

char s[MAXL];

int main()
{
    scanf("%s", s+1);
    sam.init();
    int n=strlen(s+1);
    sam.addstring(s, n);

    while(scanf("%s", s+1)==1)
    {
        int m=strlen(s+1);
        sam.match(s, m);
    }
    int res=0;
    for(int i=sam.st;i<=sam.amo;i++)
    {
        res=max(res, sam.mi[i]);
    }
    printf("%d\n", res);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值