POJ 2774求兩字符串的最長公共子串(後綴數組)

POJ 2774求兩字符串的最長公共子串(後綴數組)

題目鏈接:傳送門

思路

大概就是枚舉s2(s1)的每個後綴,查看對應的s1(s2)的後綴與自己的最長前綴長度是多少。

我們可以把字符串s1和s2拼接到一起,中間用’#‘連接(’#'只是代表比字符集任意一個字符小的字符),然後求出拼接後字符串的後綴數組,在排序後的後綴數組上排名從小到大遍歷,並同時記下左邊最近的s1(s2)的後綴的位置p1(p2),然後對於每個s2(s1)的後綴,用RMQ求出p1(p2)位置的後綴串與自己後綴的最長前綴長度是多少(這一步可以RMQ預處理O(1)查詢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);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章