后缀数组

后缀数组模板


* 以下部分代码和部分解释均来自于白书。 *
关于后缀数组的几个性质:

  1. L C P ( i , j ) = L C P ( j , i ) LCP(i, j) = LCP(j, i) LCP(i,j)=LCP(j,i)
  2. L C P ( i , k ) = m i n ( L C P ( i , j ) , L C P ( j , k ) ) LCP(i, k) = min(LCP(i, j), LCP(j, k)) LCP(i,k)=min(LCP(i,j),LCP(j,k)) 0 ≤ i ≤ j ≤ k < n 0 \leq i \leq j \leq k < n 0ijk<n
    简单证明:
    L C P ( i , j ) = p , L C P ( j , k ) = q LCP(i,j)=p, LCP(j,k)=q LCP(i,j)=p,LCP(j,k)=q p o s i = s a [ i ] , p o s j = s a [ j ] , p o s k = s a [ k ] pos_i=sa[i],pos_j=sa[j],pos_k=sa[k] posi=sa[i],posj=sa[j],posk=sa[k]
    那么,一定存在 s [ p o s i ∼ p o s i + p − 1 ] = s [ p o s j ∼ p o s j + p − 1 ] s[pos_i \sim pos_i+p-1]=s[pos_j \sim pos_j+p-1] s[posiposi+p1]=s[posjposj+p1] s [ p o s j ∼ p o s j + q − 1 ] = s [ p o s k ∼ p o s k + q − 1 ] s[pos_j \sim pos_j+q-1]=s[pos_k \sim pos_k+q-1] s[posjposj+q1]=s[poskposk+q1]
    假设 L C P ( i , k ) = x < m i n ( p , q ) LCP(i,k)=x < min(p,q) LCP(i,k)=x<min(p,q),存在 s [ p o s i + x ] ≠ s [ p o s k + x ] s[pos_i+x] \neq s[pos_k+x] s[posi+x]̸=s[posk+x]
    又因为 p , q p,q p,q 均大与 x x x,所以有 s [ p o s i + x ] = s [ p o s j + x ] s[pos_i+x] = s[pos_j+x] s[posi+x]=s[posj+x] s [ p o s j + x ] = s [ p o s k + x ] s[pos_j+x]=s[pos_k+x] s[posj+x]=s[posk+x],推出 s [ p o s i + x ] = s [ p o s k + x ] s[pos_i+x]=s[pos_k+x] s[posi+x]=s[posk+x]。与假设矛盾,所以 L C P ( i , k ) ≥ m i n ( p , q ) LCP(i,k) \geq min(p,q) LCP(i,k)min(p,q)
    同理可证明出 L C P ( i , k ) ≤ m i n ( p , q ) LCP(i,k) \leq min(p,q) LCP(i,k)min(p,q),所以 L C P ( i , k ) = m i n ( p , q ) LCP(i,k) = min(p,q) LCP(i,k)=min(p,q)
  3. L C P ( i , j ) = m i n { L C P ( k , k + 1 ) } LCP(i, j) = min \{ LCP(k, k + 1) \} LCP(i,j)=min{LCP(k,k+1)} i ≤ k < j i \leq k < j ik<j
    可以通过性质 2 2 2 推出。
  4. h e i g h t [ r a n k [ i ] ] ≥ h e i g h t [ r a n k [ i − 1 ] ] − 1 height[rank[i]] \geq height[rank[i - 1]] - 1 height[rank[i]]height[rank[i1]]1
    可以利用这个性质推出任意两个后缀的 L C P LCP LCP

ps: L C P ( i , j ) LCP(i, j) LCP(i,j) 表示 s a [ i ] sa[i] sa[i] s a [ j ] sa[j] sa[j] 两个后缀的最长公共前缀,即排名为 i i i 和排名为 j j j 的两个后缀的最长公共前缀。 h e i g h t [ i ] = L C P ( s a [ i ] , s a [ i − 1 ] ) height[i]=LCP(sa[i], sa[i-1]) height[i]=LCP(sa[i],sa[i1])

变量意义解释:
s a [ i ] sa[i] sa[i]:排名为 i i i 的后缀在原字符串的位置
r n k [ i ] rnk[i] rnk[i]:第 i i i 个后缀的排名,与 s a sa sa 数组意义相反
t m p [ i ] tmp[i] tmp[i]:辅助数组
h e i g h t [ i ] height[i] height[i]:表示 s a [ i ] sa[i] sa[i] s a [ i − 1 ] sa[i-1] sa[i1] 两个后缀的 L C P LCP LCP
c [ ] c[] c[]:基数排序用的
n n n:字符串的长度
m m m:每次需要比较的最大值的大小

代码实现:
pss: 任意位置后缀的 L C P LCP LCP 可以使用 S T ST ST 表来求,代码中只是根据性质 4 4 4 计算了任意位置的 L C P LCP LCP

const int maxn = 1e5 + 5;
string s;

int sa[maxn], rnk[maxn], tmp[maxn], c[maxn], height[maxn];
int n, m;

void build_sa()
{
    m = 128; // 初始时是以字符来比较大小的
    for(int i = 0; i < m; i++) c[i] = 0;
    // 第一次基数排序
    for(int i = 0; i < n; i++) c[rnk[i] = s[i]]++;
    for(int i = 1; i < m; i++) c[i] += c[i - 1];
    for(int i = n - 1; i >= 0; i--) sa[--c[rnk[i]]] = i;
    // 倍增计算
    for(int k = 1; k <= n; k <<= 1){
        int p = 0;
        // 利用sa数组对第二关键词进行排序
        // 第i个后缀的第二关键词就是 位置为i+k,长度为k的串
        // 这里的tmp数组和sa数组意义一样,代表的是第二关键词
        // [n - k, n) 这些位置的后缀没有第二关键词,所以第二关键词为0,此时它们的第二关键词排名是最小的
        for(int i = n - k; i < n; i++) tmp[p++] = i;
        // [0, n - k) 这些位置的后缀有第二关键词,因此可以直接根据sa数组得到第二关键词排名
        // tmp[p++] = sa[i] - k 表示在sa[i]-k位置的后缀的第二关键词的排名为p
        for(int i = 0; i < n; i++) if(sa[i] >= k) tmp[p++] = sa[i] - k;

        // 利用基数排序对第一关键词进行排序
        // 上一次迭代得到的rnk就是当前的第一关键词,rnk[tmp[i]]表示第二关键词排名为i的第一关键词
        for(int i = 0; i < m; i++) c[i] = 0;
        for(int i = 0; i < n; i++) c[rnk[tmp[i]]]++;
        for(int i = 1; i < m; i++) c[i] += c[i - 1];
        for(int i = n - 1; i >= 0; i--) sa[--c[rnk[tmp[i]]]] = tmp[i]; // 记住这里不是等于i

        // 以下是重新计算rnk数组
        swap(rnk, tmp);
        rnk[sa[0]] = 0;
        p = 1;
        // 如果排名为i和i-1的两个串的第一关键词和第二关键词相同,则它们的排名相同;否则,排名增加1
        for(int i = 1; i < n; i++)
            rnk[sa[i]] = (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + k] == tmp[sa[i - 1] + k]) ? p - 1 : p++;
        // 如果没有两个后缀的排名相同,则排序完成
        if(p >= n) break;
        // 下一次迭代需要比较的最大值
        m = p;
    }
}

void get_height()
{
    for(int i = 0, k = 0; i < n; i++){
        if(k) --k; // 性质4在这个体现
        if(rnk[i] == 0) continue;
        int j = sa[rnk[i] - 1];
        while(i + k < n && j + k < n && s[i + k] == s[j + k]) ++k;
        height[rnk[i]] = k;
    }
}

// 计算位置i和j的两个后缀的lcp
int lcp(int i, int j)
{
    if(rnk[i] > rnk[j]) swap(i, j);
    int ret = 0;
    if(i == j) ret = s.length() - i;
    else{
        ret = INT_MAX;
        for(int k = rnk[i] + 1; k <= rnk[j]; k++)
            ret = min(ret, height[k]);
    }
    return ret;
}

你可能感兴趣的:(数据结构)