poj2217 后缀数组和高度数组经典应用之找出最长公共子串

  注意这题不是公共子序列, 子串和子序列的区别在于子序列可以不连续而子串必须连续, 题目连接:http://poj.org/problem?id=2217, 我们可以将两个子串合并到一起, 中间使用$符号隔开, 然后找到最大的lcp即可, 注意对于最大的lcp[i], sa[i], sa[i+1](即最长公共前缀的起点)应该位于$两边。代码如下:

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

using namespace std;
const int maxn = 20000 + 100;
string s1, s2, s3;

int n, k;
int rk[maxn], tmp[maxn], sa[maxn], lcp[maxn];

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

void construct_sa(string s, int sa[]){   //处理后缀数组
    n = s.length();
    for(int i=0; i<=n; i++){
        sa[i] = i;
        rk[i] = i<n?s[i]:-1;
    }
    for(k=1; k<=n; k*=2){
        sort(sa, sa+n+1, cmp_sa);
        tmp[sa[0]] = 0;
        for(int i=1; i<=n; i++){
            tmp[sa[i]] = tmp[sa[i-1]] + (cmp_sa(sa[i-1], sa[i])?1:0);
        }
        for(int i=0; i<=n; i++) rk[i] = tmp[i];
    }
}

void construct_lcp(string s, int sa[], int lcp[]){    //处理高度数组
    int n = s.length();
    for(int i=0; i<=n; i++) rk[sa[i]] = i;
    int h = 0;
    lcp[0] = 0;
    for(int i=0; i<n; i++){
        int j = sa[rk[i]-1];   //获取后缀数组中比i后缀小的那个后缀
        if(h > 0) h--;
        for(; j+h<n&&i+h<n; h++){
            if(s[j+h] != s[i+h])  break;
        }
        lcp[rk[i]-1] = h;
    }
}
char str[10000 + 100];
int main() {
    int T;
    scanf("%d", &T); getchar();
    while(T--){
        gets(str);  s1 = str; //cout<<str<<endl;
        gets(str);  s2 = str; //cout<<str<<endl;
        int len1 = s1.length();
        s3 = s1 + '$' + s2;
//        s3 = s1;
        construct_sa(s3, sa);                     //求解s3的后缀数组
//        printf("s3.length = %d\n", n);
//        for(int i=0; i<=n; i++) printf("%d%c", sa[i], i==n?'\n':' ');
        construct_lcp(s3, sa, lcp);               //求解s3的高度数组
//        for(int i=0; i<=n; i++) printf("%d%c", lcp[i], i==n?'\n':' ');
        int ans = 0;
        for(int i=0; i<n; i++){
            if((sa[i]<len1) != (sa[i+1]<len1)) //最长公共前缀的起点在$两边
                ans = max(ans, lcp[i]);
        }
        printf("Nejdelsi spolecny retezec ma delku %d.\n", ans);
    }
    return 0;
}

 

posted @ 2016-03-28 17:18  xing-xing  阅读(206)  评论(0编辑  收藏  举报