后缀数组小记

求 sa, rnk

定义

\(sa[i]\) 表示第 \(i\) 小的后缀对应原串的位置
\(rk[i]\) 表示第 \(i\) 个后缀的排名
\(x[i]\) 表示第 \(i\) 个后缀的第一关键字排名,即当前的 \(rk[i]\)
\(y[i]\) 表示第 \(i\) 小的第二关键字对应的第几个后缀
\(c[i]\) 是一个计数数组,用于基数排序用

方法

考虑倍增,每次从 \(2^k\) 转移到 \(2^{k+1}\) ,可以发现每个 \(2^{k+1}\) 串可以表示为两个 \(2^k\) 的子串,分别对应两个关键字。
用基数排序即可。
复杂度 \(O(nlogn)\)

求 LCP

定义 \(LCP(i,j)\) 表示 \(suf(sa[i])\)\(suf(sa[j])\)最长公共前缀长度。

引理

显然有:

  • \(LCP(i,j) = LCP(j,i)\)
  • \(LCP(i,i) = n - sa[i] + 1\)

LCP Lemma

考虑一个强大的结论:
对于任意 \(1\le i< k< j\le n\),均有 \(LCP(i,j)=min(LCP(i,k),LCP(k,j))\)
证明:
\(p=min(LCP(i,k),LCP(k,j))\) ,则有 \(LCP(i,k)\ge p, LCP(k,j)\ge p\)
\(suf(sa[i]) = u, suf(sa[k]) = v, suf(sa[j]) = w\)
则由 \(LCP\) 定义知 (\(u\)\(v\))、(\(v\)\(w\)) 前 \(p\) 个字符均相等。
因此 \(u\)\(w\) 的前 \(p\) 个字符相等,所以 \(LCP(i,j)\ge p\)
假设 \(LCP(i,j)=q > p\) ,那么 \(q\ge p + 1\) ,即 \(u[p+1]=w[p+1]\)
因为 \(p=min(LCP(i,k),LCP(k,j))\) ,所以 \(u[p+1]≠v[p+1]\)\(v[p+1]≠w[p+1]\)
因为 \(suf(sa[i])<suf(sa[j])<suf(a[k])\) ,所以 \(u[p+1]=v[p+1]=w[p+1]\)
矛盾,故 \(LCP(i,j)\le p\)
因此 \(LCP(i,j)=p=min(LCP(i,k),LCP(k,j))\)

LCP Theorem

终极结论:
\(LCP(i,j)=min(LCP(k-1,k) | i < k\le j)\)
证明:
\(LCP\ Lemma\)\(LCP(i,j)=min(LCP(i,j-1),LCP(j-1,j))=min(min(LCP(i,j-2),LCP(j-2,j-1)),LCP(j-1,j))=...=min(LCP(k-1,k) | i < k\le j)\)

推导

前面都是铺垫。
我们设 \(height[i]=LCP(i-1,i)\) (\(1<i\le n\)) ,显然 \(height[1] = 0\)
\(LCP\ Theorem\)\(LCP(i,j)=min(height[k] | i<k\le j)\)
那么,这个 \(height\) 该咋求呢?
继续定义 \(h[i] = height[rk[i]]\) ,则有 \(height[i] = h[sa[i]]\)
下面来证明一个最关键的定理:\(h[i]\ge h[i-1]-1\) ,即 \(height[rk[i]]\ge height[rk[i-1]] - 1\)
也即 \(LCP(rk[i]-1,rk[i])\ge LCP(rk[i-1]-1,rk[i-1])-1\)
证明:
不妨设第 \(i-1\) 个字符串按排名来前面的是第 \(k\) 个字符串。
\(height\) 定义知第 \(k\) 个字符串和第 \(i-1\) 个字符串的 \(LCP\)\(height[rk[i-1]]\)
下面我们来讨论两字符串间的关系:

  • \(k\) 个字符串和第 \(i-1\) 个字符串的首字符不同

那么第 \(k+1\) 个字符串的排名既有可能在 \(i\) 前面,也有可能在 \(i\) 后面。
但没关系,此时 \(height[rk[i-1]]=0\) ,一定满足 \(height[rk[i]] \ge height[rk[i-1]] - 1\)

  • \(k\) 个字符串和第 \(i-1\) 个字符串的首字符相同

由于第 \(k+1\) 个字符串就是第 \(k\) 个字符串去掉首字符得到的,第 \(i\) 个字符串就是第 \(i-1\) 个字符串去掉首字符得到的,所以第 \(k+1\) 个字符串和第 \(i\) 个字符串的 \(LCP\) 就是 \(height[rk[i-1]]-1\)
注意第 \(k+1\) 个字符不一定是第 \(sa[rk[i]-1]\) 个字符串,但是因为第 \(sa[rk[i]-1]\) 个字符串和第 \(i\) 个字符串的 \(LCP\) 最大,即 \(height[rk[i]]=LCP(rk[i]-1,rk[i])\ge LCP(rk[k+1],rk[i])=height[rk[i-1]]-1\)

综上所述,可证得 \(h[i]\ge h[i-1]-1\)

贴份代码,跑路

// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define debug(x) cerr << #x << " = " << x << '\n';
#define pll pair <ll, ll>
#define fir first
#define sec second

inline int read() {
  int x = 0, neg = 1; char op = getchar();
  while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
  while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
  return neg * x;
}
inline void print(int x) {
  if (x < 0) { putchar('-'); x = -x; }
  if (x >= 10) print(x / 10);
  putchar(x % 10 + '0');
}

const int N = 1000005;

char s[N];
int n, m;

int sa[N], rk[N], x[N], y[N];
int cnt[N];
void SA() {
  for (rint i = 1; i <= m; i++) cnt[i] = 0;
  for (rint i = 1; i <= n; i++) cnt[x[i] = s[i]]++;
  for (rint i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
  for (rint i = n; i >= 1; i--) sa[cnt[x[i]]--] = i;
  int p;
  for (rint k = 1; k <= n; k <<= 1) {
    int cur = 0;
    for (rint i = n - k + 1; i <= n; i++) y[++cur] = i;
    for (rint i = 1; i <= n; i++) if (sa[i] > k) y[++cur] = sa[i] - k;

    for (rint i = 1; i <= m; i++) cnt[i] = 0;
    for (rint i = 1; i <= n; i++) cnt[x[i]]++;
    for (rint i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
    for (rint i = n; i >= 1; i--) sa[cnt[x[y[i]]]--] = y[i];

    swap(x, y);
    x[sa[1]] = 1, p = 1;
    for (rint i = 2; i <= n; i++) {
      x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p : ++p);
    }
    if (p == n) break;
    m = p;
  }
  memcpy(rk, x, sizeof(x));
}

int height[N], h[N][20], lg[N];
int getheight() {
  int k = 0;
  for (rint i = 1; i <= n; i++) {
    if (rk[i] == 1) continue;
    if (k) --k;
    int j = sa[rk[i] - 1];
    while (j + k <= n && i + k <= n && s[i + k] == s[j + k]) k++;
    height[rk[i]] = k;
    h[rk[i]][0] = k; 
  }
  for (rint j = 1; j < 20; j++) {
    for (rint i = 1; i + (1 << j) - 1 <= n; i++) {
      h[i][j] = min(h[i][j - 1], h[i + (1 << j - 1)][j - 1]);
    }
  }
}
int LCP(int l, int r) {
  if (l > r) swap(l, r);
  if (l == r) return n - sa[l] + 1;
  l++;
  int LEN = lg[r - l];
  return min(h[l][LEN], h[r - (1 << LEN) + 1][LEN]);
}

int main() {
  scanf("%s", s + 1);
  n = strlen(s + 1);
  m = 150;
  SA();
  getheight();
  for (rint i = 1; i <= n; i++) {
    printf("%d ", sa[i]);
  }
  puts("");

  lg[1] = 0;
  for (rint i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
  int l, r;
  while (~scanf("%d%d", &l, &r)) {
    printf("%d\n", LCP(rk[l], rk[r]));
  }
  return 0;
}

原文链接: https://www.cnblogs.com/wlzhouzhuan/p/13244529.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    后缀数组小记

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/362768

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月2日 下午2:33
下一篇 2023年3月2日 下午2:34

相关推荐