POJ 2774求两字符串的最长公共子串(后缀数组)

POJ 2774求两字符串的最长公共子串(后缀数组)

题目链接:传送门

思路

大概就是枚举s2(s1)的每个后缀,查看对应的s1(s2)的后缀与自己的最长前缀长度是多少。

我们可以把字符串s1和s2拼接到一起,中间用’#‘连接(’#'只是代表比字符集任意一个字符小的字符),然后求出拼接后字符串的后缀数组,在排序后的后缀数组上排名从小到大遍历,并同时记下左边最近的s1(s2)的后缀的位置p1(p2),然后对于每个s2(s1)的后缀,用RMQ求出p1(p2)位置的后缀串与自己后缀的最长前缀长度是多少(这一步可以RMQ预处理O(1)查询 h e i g h t [ ] height[] height[]区间最小值实现),更新最长前缀长度的最大值即可。

代码

//#include<bits/stdc++.h>
#include<algorithm>
#include<stdio.h>
#include<iostream>
#include<string.h>
#define mset(a,b)   memset(a,b,sizeof(a))
typedef long long ll;
const int N=2e5+10;
int t1[N],t2[N],c1[N];//辅助数组
void SA(char *s,int n,int sa[],int rank[],int height[])//基数排序的版本中
{
    int m=128;//桶的大小,会在下面循环中变化,第一关键词r的最大值,初始时是字符的最大值,之后都<=n
    int *sb=t1,*r=t2,*c=c1;//辅助数组,分别为:基数排序所用的第二2,rak',cnt数组
    //用基数排序求出长度为1的sa[]和rank[],如果字符最大值较大,第一轮可以采用sort
    for(int i=0; i<=m; ++i) c[i]=0;
    for(int i=1; i<=n; ++i) c[r[i]=s[i]]++;
    for(int i=1; i<=m; ++i) c[i]+=c[i-1];
    for(int i=n; i>=1; --i) sa[c[s[i]]--]=i;

    for(int k=1,p ; k < n; k<<=1 ) //p是一个计数器,现在还没用。
    {
        //sb[i]:第二关键词排名为i的位置为sb[i]
        p=0;
        for(int i=n-k+1; i<=n; ++i) sb[++p]=i;
        for(int i=1; i<=n; ++i) if(sa[i]>k) sb[++p]=sa[i]-k;
        //基数排序求出2k长度的sa数组
        for(int i=0; i<=m; ++i) c[i]=0;
        for(int i=1; i<=n; ++i) c[r[i]]++;
        for(int i=1; i<=m; ++i) c[i]+=c[i-1];
        for(int i=n; i>=1; --i) sa[ c[r[sb[i]]]-- ]=sb[i];
        std::swap(r,sb);
        //现在要利用上轮的r和这轮的sa求这轮的r
        r[sa[1]]=p=1;
        for(int i=2,a,b; i<=n; ++i)
        {
            a=sa[i],b=sa[i-1];
            if(sb[a]==sb[b]&&(a+k<=n && b+k<=n&&sb[a+k]==sb[b+k]) ) r[a]=p;
            else r[a]=++p;
        }
        if(p>=n) break;//可以提前退出
        m=p;
    }
    /*计算高度数组*/
    int k=0;
    for(int i=1; i<=n; ++i) rank[sa[i]]=i;
    height[1]=0;
    for(int i=1; i<=n; ++i)
    {
        if(k) k--;
        int j=sa[rank[i]-1];
        if(j==0) continue;
        while(s[j+k]==s[i+k]) ++k;
        height[rank[i]]=k;
    }
}
int rank[N],sa[N],height[N]; //height[1]的值为0
char s[N];
//预处理O(nlogn)—查询O(1)求height[]的连续最小值的RMQ代码
int RMQ[N],mm[N],best[N][20];//rmq[]=height[]
void initRMQ(int n, int height[])
{
    for(int i=1; i<=n; ++i) RMQ[i]=height[i];
    mm[0]=-1;
    for(int i=1; i<=n; ++i) mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
    for(int i=1; i<=n; ++i) best[i][0]=i;
    for(int j=1; j<=mm[n]; ++j)
        for(int i=1; i+(1<<j)-1<=n; ++i)
        {
            int a=best[i][j-1];
            int b=best[i+(1<<(j-1))][j-1];
            if(RMQ[a] < RMQ[b]) best[i][j]=a;
            else best[i][j]=b;
        }
}
int askRMQ(int a,int b)//求区间[a,b]最小值的下标
{
    int t;
    t=mm[b-a+1];
    b-=(1<<t)-1;
    a=best[a][t],b=best[b][t];
    if(RMQ[a] < RMQ[b]) return a;
    else return b;
}
int lcp(int a,int b)
{
    a=rank[a],b=rank[b];
    if(a > b) std::swap(a,b);
    return height[askRMQ(a+1,b)];
}
char s1[N],s2[N];
int main()
{
    int n=0;
    scanf("%s%s",s1+1,s2+1);
    int ls1=strlen(s1+1),ls2=strlen(s2+1);
    for(int i=1; i<=ls1; ++i) s[++n]=s1[i];
    s[++n]='#';
    for(int i=1; i<=ls2; ++i) s[++n]=s2[i];
    s[n+1]='\0';
    SA(s,n,sa,rank,height);
    initRMQ(n,height);
    int lp1=-1,lp2=-1,ans=0;
    for(int i=1; i<=n; ++i)
    {
        if(sa[i]>ls1+1)//是串s2
        {
            if(lp1!=-1)
                ans=std::max(ans,height[askRMQ(lp1+1,i)]);
            lp2=i;
        }
        else if(sa[i]<=ls1)
        {
            if(lp2!=-1)
                ans=std::max(ans,height[askRMQ(lp2+1,i)]);
            lp1=i;
        }
    }
    printf("%d\n",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值