zoukankan      html  css  js  c++  java
  • 后缀数组(SA)备忘

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

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

    这篇博客 代码注释特别好

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

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

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

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

    trick:

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

    本质不同的子串个数 = (frac{n imes (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。

  • 相关阅读:
    博客园页面设置(转载)
    正则表达式30分钟入门教程 (转载)
    如何写出优雅的代码
    centos7 nginx+php5.6+mysql安装与配置
    git 进阶
    js 异步解决方案
    行动派
    unicode 与 utf-8
    bower command not found--windows
    click事件细节
  • 原文地址:https://www.cnblogs.com/storz/p/10593908.html
Copyright © 2011-2022 走看看