EOJ1805 POJ2217 最长公共子串(后缀数组+高度数组)

最长公共子串不同于最长公共子序列,子序列不要求连续,但子串顾名思义需要连续。
至于应用方面,我觉得最长公共子串算法可以应用于基因片段匹配。
EOJ1805上字符串的长度超过5万,后缀数组来解决此题不会超时,甚至可以说是解决此题的唯一算法。
对于构造sa数组非常的巧妙,和RMQ算法的思想很相似(都用到了分治的思想),先按一个字符组成的串来排序,再按两个字符组成的串,进而四个……直到sa数组按照后缀(即从(i…L)的子串)来排序,至此后缀数组构建完成。代码如下:

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

void Construct_sa()
{
    n = s.size();
    for (int i = 0; i <= n; i++){
        sa[i] = i;
        rk[i] = i < n ? s[i] : -1;
    }
    for (k = 1; k <= n; k <<= 1)
    {
        sort(sa, sa + n + 1, cmp);
        tmp[sa[0]] = 0;
        for (int i = 1; i <= n; i++)
        {
            tmp[sa[i]] = tmp[sa[i - 1]] + cmp(sa[i - 1], sa[i]) ;
        }
        for (int i = 0; i <= n; i++){
            rk[i] = tmp[i];
        }
    }
}

可知,构建后缀数组的时间复杂度为O(nlogn)。

为了求公共子串,就不得不引入高度数组,就是对于排好的后缀数组,探究相邻两后缀的公共前缀长度。为了节省时间,利用已经有序的后缀数组,比较公共前缀长度时,不是每一次都需要从串首开始,代码如下:

void Construct_h()
{
    n = s.size();
    for (int i = 0; i <= n; i++) rk[sa[i]] = i;
    int k = 0;
    for (int i = 0; i < n; i++){
        int j = sa[rk[i] - 1];
        if (k) k--;//下面证明
        for ( ; j + k < n&&i + k < n&&s[j + k] == s[i + k]; k++);
        h[rk[i] - 1] = k;
    }
}

证明:h数组有以下性质:
h[i]≥h[i-1]-1

设 suffix(j)是排在 suffix(i-1)前一名的后缀,则它们的最长公共前缀是h[i-1]。
当h[i-1]>1时,我们不妨设suffix(j) 为”abcde…”suffix(i-1)为 “abdef…”,前后对照可知它们的最长公共前缀长度为2,现在考虑suffix(i)=”bdef..”,那么对于排在它前面的,有一个下界,是suffix(j+1)=”bcef..” 。假如suffix(j+1)刚好排在suffix(i)的前一个,那么他们的最长公共前缀=1,此时h[i]=h[i-1]-1。如果suffix(j+1)不排在suffix(i)的前一个,在suffix(j+1)和suffix(i)之间插进了一个suffix(k),可想而知suffix(k)既要比suffix(j+1)大,又要比suffix(i)小,那么它的第一个字符一定是’b’,第二个字符一定在’c’和’d’之间,可知suffix(k)与suffix(i)的最长公共前缀>=suffix(j+1)与suffix(i)的最长公共前缀,既h[i]≥h[i-1]-1恒成立。
而当h[i-1]<=1时,h[i-1]-1<=0,由h[]数组的定义可知,h[i]>=0,所以h[i]≥h[i-1]-1恒成立。

按照 h[1],h[2],……,h[n]的顺序计算,利用 h 数组的性质,时间复杂度可降为 O(n)。

之后的操作将在main函数中实现,对于给定的两个字符串,在其间加一个两字符串中不会出现的字符(比如空格),连成一个字符串,然后进行sa数组的构建和h数组的计算。要在sa[i]和sa[i+1]分属两个字符串的前提下,找出h[i]的最大值,即为题目的answer!

核心代码如下:

Construct_sa();
Construct_h();
int len = s.size();
int ans = 0;
for(int i=0; iif((sa[i] < len1) != (sa[i+1] < len1))//len1为第一个字符串长度
         ans=max(ans,h[i]);

如果对于多个串的最长公共子串呢?要用到二分,将在以后细细叙说。(by Datow)

你可能感兴趣的:(EOJ,后缀数组,POJ)