后缀数组小记

求 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

考虑一个强大的结论:
对于任意 (1le i< k< jle 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) ,那么 (qge 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 < kle 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 < kle j))

推导

前面都是铺垫。
我们设 (height[i]=LCP(i-1,i)) ((1<ile n)) ,显然 (height[1] = 0)
(LCP Theorem)(LCP(i,j)=min(height[k] | i<kle 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 << '
';
#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
", LCP(rk[l], rk[r]));
  }
  return 0;
}
原文地址:https://www.cnblogs.com/wlzhouzhuan/p/13244529.html