1. 程式人生 > >POJ 2217 (最長公共子串)

POJ 2217 (最長公共子串)

先考慮一個簡單的問題,計算一個字串中至少出現兩次以上的最長子串,答案一定會在後綴陣列中相鄰兩個字尾的公共字首之中,所有隻要考慮他們就好了,原因是子串的開始位置在後綴中相距越遠,其公共字首的長度也就越短,因此,高度陣列的最大值就是答案。

再考慮這個問題的解法,因為對於兩個字串,不好直接運用字尾陣列,所以我i們可以在S,T中間插入一個不會出現的字元('\0')拼成一個字串  S''  ,然後,計算  S‘’   的字尾陣列,檢查字尾陣列的所有相鄰字尾,其中,分屬於S和T的不同字串的字尾的lcp的最大值就是答案。而要知道字尾時屬於S還是T,可以由其在  S''  中的位置直接判斷。

#include<cstdio>
#include<algorithm>
#include<string>
#include<iostream>
#include<cstring>

using namespace std;

const int maxn=1e4+10;

int rankk[maxn];
int tmp[maxn];
int k,n;
string s,t;
int sa[maxn],lcp[maxn];

bool compare_sa(int i,int j){
    if(rankk[i]!=rankk[j]){
        return rankk[i]<rankk[j];
    }else{
        int ri=i+k<=n?rankk[i+k]:-1;
        int rj=j+k<=n?rankk[j+k]:-1;
        return ri<rj;
    }
}

void construct_sa(string s,int *sa){
    for(int i=0;i<=n;i++){
        sa[i]=i;
        rankk[i]=i<n?s[i]:-1;
    }
    for(k=1;k<=n;k<<=1){
        sort(sa,sa+n+1,compare_sa);
        tmp[sa[0]]=0;
        for(int i=1;i<=n;i++){
            tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0);
        }
        for(int i=0;i<=n;i++){
            rankk[i]=tmp[i];
        }
    }
}

void construct_lcp(string s,int *sa,int *lcp){
    int n=s.size();
    for(int i=0;i<=n;i++){
        rankk[sa[i]]=i;
    }
    int h=0;
    lcp[0]=0;
    for(int i=0;i<n;i++){
        int j=sa[rankk[i]-1];
        if(h) h--;
        for(;j+h<n&&i+h<n;h++){
            if(s[j+h]!=s[i+h]) break;
        }
        lcp[rankk[i]-1]=h;
    }
}

void solve(){
    int s1=s.size();
    s=s+'\0'+t;
    construct_sa(s,sa);
    construct_lcp(s,sa,lcp);
    int ans=0;
    for(int i=0;i<n;i++){
        if((sa[i]<s1)!=(sa[i+1]<s1)){
            ans=max(ans,lcp[i]);
        }
    }
    cout<<"Nejdelsi spolecny retezec ma delku "<<ans<<"."<<endl;
}

int main(){
    ios::sync_with_stdio(0);
    int cs;
    cin>>cs;
    cin.ignore();
    while(cs--){
        getline(cin,s);
        getline(cin,t);
        n=s.size()+t.size()+1;
        solve();
    }
    return 0;
}