后缀数组小记
求 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;
}