后缀数组(SA)备忘

两个让我真正理解代码的资料:

2009集训队论文 网上的经典应用都是从里面抄的,还把解释给去掉了。。。真事屑

这篇博客 代码注释特别好

桶排换成快排的代码,便于理解算法思想

,这里面要减去k的原因是 sa[i] 作为 sa[i]-k 的第二关键字

循环中m=x说明所有后缀长度为x的子串已经排好,要长度为更新2m的答案

"y[i]表示第二关键字排名为i的数,第一关键字的位置"

trick:

如果s[i]太大可以先离散化一下

本质不同的子串个数 = \(\frac{n\times (n+1)}{2}\) - height数组的和

get_SA里的Y和rnk每次用完要清零 否则多组数据会出锅,字符串n+1位置记得弄成0

倒过来建SA可以实现一些例如查LCS之类的操作

把字符串拼起来建SA可以同时比较多个字符串的后缀,中间用不同的分隔符隔开(注意爆char)

struct SA {
  int k, sa[N], rnk[N], H[N], st[N][17];
  char s[N];
  bool cmp(int *y, int a, int b, int m) {return y[a] == y[b] && y[a + m] == y[b + m];}
  void Sort(int *x, int *y, int *rk) {
    static int C[N];
    for (int i = 0; i <= k; ++i) C[i] = 0;
    for (int i = 1; i <= n; ++i) ++C[rk[i]];
    for (int i = 1; i <= k; ++i) C[i] += C[i - 1];
    for (int i = n; i; --i) y[C[rk[x[i]]]--] = x[i];
  }
  void get_SA() {
    static int Y[N];
    int *y = Y, *rk = rnk;
    k = 128;
    for (int i = 1; i <= n; ++i) rk[i] = s[y[i] = i];
    Sort(y, sa, rk);
    for (int m = 1, p = 0; p < n; k = p, m <<= 1) {
      for (p = 0; p < m; ++p) y[p + 1] = n - m + p + 1;
      for (int i = 1; i <= n; ++i) if (sa[i] > m) y[++p] = sa[i] - m;
      Sort(y, sa, rk), swap(rk, y);
      rk[sa[p = 1]] = 1;
      for (int i = 2; i <= n; ++i) rk[sa[i]] = cmp(y, sa[i], sa[i - 1], m) ? p : ++p;
    }
    for (int i = 1; i <= n; ++i) rnk[sa[i]] = i, Y[i] = 0;
  }
  void get_H() {
    for (int i = 1, k = 0; i <= n; H[rnk[i++]] = k)
      for (k ? --k : 0; s[i + k] == s[sa[rnk[i] - 1] + k]; ++k);
    for (int i = 2; i <= n; ++i) st[i][0] = H[i];
    for (int j = 1; j <= __lg(n); ++j)
      for (int i = 2; i + (1 << j) - 1 <= n; ++i)
        st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
  }
  void clear() {
    memset(rnk, 0, sizeof rnk);
  }
  int lcp(int x, int y) {//求后缀x和y的lcp
    x = rnk[x], y = rnk[y];
    if (x > y) swap(x, y);
    ++x;
    int k = __lg(y - x + 1);
    return min(st[x][k], st[y - (1 << k) + 1][k]);
  }
};

求height复杂度一句话证明:k减小O(n)次,增加O(n)次

下文引自ID为远航之曲 (blog已经挂掉了)神犇的博客

能够线性计算height[]的值的关键在于h的性质,即h[i]>=h[i-1]-1,下面具体分析一下这个不等式的由来。

我们先把要证什么放在这:对于第i个后缀,设j=sa[rank[i] – 1],也就是说j是i的按排名来的上一个字符串,按定义来i和j的最长公共前缀就是height[rank[i]],我们现在就是想知道height[rank[i]]至少是多少,而我们要证明的就是至少是height[rank[i-1]]-1。

好啦,现在开始证吧。

首先我们不妨设第i-1个字符串(这里以及后面指的“第?个字符串”不是按字典序排名来的,是按照首字符在字符串中的位置来的)按字典序排名来的前面的那个字符串是第k个字符串,注意k不一定是i-2,因为第k个字符串是按字典序排名来的i-1前面那个,并不是指在原字符串中位置在i-1前面的那个第i-2个字符串。

这时,依据height[]的定义,第k个字符串和第i-1个字符串的公共前缀自然是height[rank[i-1]],现在先讨论一下第k+1个字符串和第i个字符串的关系。

第一种情况,第k个字符串和第i-1个字符串的首字符不同,那么第k+1个字符串的排名既可能在i的前面,也可能在i的后面,但没有关系,因为height[rank[i-1]]就是0了呀,那么无论height[rank[i]]是多少都会有height[rank[i]]>=height[rank[i-1]]-1,也就是h[i]>=h[i-1]-1。

第二种情况,第k个字符串和第i-1个字符串的首字符相同,那么由于第k+1个字符串就是第k个字符串去掉首字符得到的,第i个字符串也是第i-1个字符串去掉首字符得到的,那么显然第k+1个字符串要排在第i个字符串前面,要么就产生矛盾了。同时,第k个字符串和第i-1个字符串的最长公共前缀是height[rank[i-1]],那么自然第k+1个字符串和第i个字符串的最长公共前缀就是height[rank[i-1]]-1。

到此为止,第二种情况的证明还没有完,我们可以试想一下,对于比第i个字符串的字典序排名更靠前的那些字符串,谁和第i个字符串的相似度最高(这里说的相似度是指最长公共前缀的长度)?显然是排名紧邻第i个字符串的那个字符串了呀,即sa[rank[i]-1]。也就是说sa[rank[i]]和sa[rank[i]-1]的最长公共前缀至少是height[rank[i-1]]-1,那么就有height[rank[i]]>=height[rank[i-1]]-1,也即h[i]>=h[i-1]-1。

posted @ 2019-03-25 15:15  QvvQ  阅读(399)  评论(1编辑  收藏  举报