后缀数组 最长公共子串

时间限制:5000ms
单点时限:1000ms
内存限制:256MB
描述

小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品中的旋律有共同的部分。

旋律是一段连续的数列,如果同一段旋律在作品A和作品B中同时出现过,这段旋律就是A和B共同的部分,比如在abab 在 bababab 和 cabacababc 中都出现过。小Hi想知道两部作品的共同旋律最长是多少?

解题方法提示

输入

共两行。一行一个仅包含小写字母的字符串。字符串长度不超过 100000。

输出

一行一个整数,表示答案。

样例输入
abcdefg
abacabca
样例输出
3

小Ho:这一次的问题该如何解决呢?

小Hi:嗯,这次的问题是经典的最长公共子串问题。

小Ho:我以前学过kmp,但是似乎不适用这道题目。

小Hi:是的。问题的关键就出在kmp求的是完整的匹配,而本题需要支持子串的匹配。

小Ho:那怎么用后缀数组解决呢?后缀数组求的是一个串的呀。

小Hi:对。但是你有没有想过可以把两个串拼起来成为一个串?

小Ho:啊!好妙的思路。

小Hi:我们不妨将两个串用一个没出现过的#字符隔开。对这个拼接串求后缀数组和height 数组。

小Ho:喔!既然height和两两后缀之间的最长公共前缀有关,那是不是height的最大值就是答案呀?

小Hi:只说对了一部分。直接这样子做是不对的。举个例子abab和a,我们对abab#a求后缀数组,得到:

suffix sa height belong

a 5 0 /

a 6 0 a
ab#a 3 1 abab
abab#a 1 2 abab
b#a 4 0 abab
bab#a 2 1 abab
我们发现height的最大值是2,而正确答案显然是1。

小Ho:这是为什么?

小Hi:由于例子中ab#a和abab#a两个后缀的开始位置同属于前一个字符串,导致计算出了前一个字符串内部的”公共子串”。

小Ho:哦,我明白了。我想想怎么修改这个算法…

小Hi:其实很简单,强行把他们分离就好了。

小Ho:是不是我们只需求排名相邻,原来不在同一个字符串的 height 值的最大值。

小Hi:为什么?

小Ho:你想啊,如果两个后缀在不同串中,计算它们最长前缀时必定要跨越过这些height值。举个例子,比如上面例子中求abab和a的最长前缀时(对应后缀数组中第4个和第2个),我们跨越了第2个后缀和第3个后缀这个不同串的“分界处”。

小Hi:说的太对了!

小Ho:我这就去实现一下!

小Hi:这个做法可以推广到做任意多个串的最长公共子串,如果你有兴趣也可以好好想想。

小Ho:嗯,应该也难不倒我。就把独立思考留给我吧!

‘#’ 不属于第一个串

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

const int MAXN = 210000;
int t1[MAXN],t2[MAXN],c[MAXN];

bool cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}

void da(char str[],int sa[],int ra[],int height[],int n,int m)
{
    n++;
    int i,j,p,*x=t1,*y=t2;
    for(i=0;i<m;i++) c[i]=0;
    for(i=0;i<n;i++) c[x[i]=str[i]]++;
    for(i=1;i<m;i++) c[i]+=c[i-1];
    for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    for(j=1;j<=n;j<<=1)
    {
        p=0;
        for(i=n-j;i<n;i++) y[p++]=i;
        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(i=0;i<m;i++) c[i]=0;
        for(i=0;i<n;i++) c[x[y[i]]]++;
        for(i=1;i<m;i++) c[i]+=c[i-1];
        for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;x[sa[0]]=0;
        for(i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p>=n) break;
        m=p;
    }
    int k=0;
    n--;
    for(i=0;i<=n;i++) ra[sa[i]]=i;
    for(i=0;i<n;i++) 
    {
        if(k) k--;
        j=sa[ra[i]-1];
        while(str[i+k]==str[j+k])k++;
        height[ra[i]]=k;
    }
}
int ra[MAXN],height[MAXN];


int sa[MAXN];
char str[MAXN];


int main()
{

    scanf("%s",str);
    int l1=strlen(str);
    str[l1]='#';
    scanf("%s",str+l1+1);
    int n=strlen(str);
    str[n]=0;

    da(str,sa,ra,height,n,128);

    int ans=0;
    for(int i=2;i<=n;i++)
    {
        if(sa[i]>=l1&&sa[i-1]<l1||sa[i]<l1&&sa[i-1]>=l1) 
            ans=max(ans,height[i]);
    }
    printf("%d\n",ans );

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值