1. 程式人生 > 其它 >【筆記】字尾陣列

【筆記】字尾陣列


time: 2021-12-09 14:13:13
tags:

  • 筆記 字串/字尾陣列

題解 P3809 【【模板】字尾排序】 - xMinh 的部落格 - 洛谷部落格 (luogu.com.cn)

記號:

\(s_i\):字串 \(s\) 的第 \(i\) 個字尾。

\(t_i\):排好序後第 \(i\) 個字串。

\(LCP(s_i,s_j)\)\(LCP(i,j)\):字串 \(s\)\(i\) 個和第 \(j\) 個字尾的 LCP 長度。

\(rk\):原陣列到排好序的陣列的對映。

\(sa\):排好序的字尾陣列到原陣列的對映。

\(height_i\)\(LCP(h_{i-1},h_i)\)

\(h_i\)\(height[sa[i]]\)

LCP lemma

\[LCP(s_i,s_j)=\min_{k=\min(rk[i],rk[j])+1}^{\max(rk[i],rk[j])}(height_k) \]

首先證 \(RHS\ge LHS\),也就是相鄰兩個的 LCP 必然大於等於 \(s_i\)\(s_j\) 的 LCP。

abcd
abcx
abcd
abcd

考慮上面的情形,abcx 與前後的 LCP 小於 4,於是它不可能在字典序中出現在該位置。

然後證存在 \(height_k=LCP(s_i,s_j)\),因為如果所有的 \(height_k\) 都大於 \(LCP(s_i,s_j)\)

,就會以一種傳遞性導致 \(LCP(s_i,s_j)\) 變大。

abcde
abcde
abcde
...
abcde

「相似度」引理

\[LCP(s_1,s_i)\le LCP(s_2,s_i)\le\dots \le LCP(s_{i-1},s_i) \]

同理,有

\[LCP(s_i,s_{i+1})\ge LCP(s_i,s_{i+2})\ge\dots\ge LCP(s_i,s_n) \]
abcdex
abcxxx
abcdef

反證法,如果存在 \(k<j\) 使得 \(LCP(s[rk[k]],s[rk[i]])>LCP(rk[j],rk[i])\),那麼它必然在字典序中 \(i\)

\(j\) 的中間。

\(h[i]\ge h[i-1]-1\)

考慮第 \(i-1\) 個字尾在排好序陣列中前一個字串 \(k\),有

\[LCP(i,k+1)=\begin{cases} 0,&LCP(i-1,k)=0\\ LCP(i-1,k)-1,&LCP(i-1,k)>0 \end{cases} \]

總之有 \(LCP(i,k+1)\ge h_{i-1}-1\). 考慮到 \(k\) 在字典序彙總排在 \(i-1\) 的前面,而 \(i\)\(k+1\)\(i-1\)\(k\) 去掉首字元得到,所以 \(k+1\) 在字典序中也排在 \(i\) 的前面。由「相似度」引理知 \(h_i=LCP(i,sa[rk[i]-1]])\ge LCP(i,k+1)\ge h_{i-1}-1\).

於是按照 \(i=rk[1],rk[2],\dots,rk[n]\) 的順序計算 \(h_i\),便可做到 \(O(n)\) 複雜度求 height.

程式碼

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 5;
int n, m, cnt, t[N], sa[N], rk[2 * N], l[2 * N];
char c[N];

void buildSA() {
	m = (int)'z';
	for(int i = 1; i <= n; i++) ++t[rk[i] = c[i]];
	for(int i = 1; i <= m; i++) t[i] += t[i - 1];
	for(int i = n; i >= 1; i--) sa[t[rk[i]]--] = i;
	for(int k = 1; k < n; k <<= 1) {
		// 按第二關鍵字排序 
		for(int i = n - k + 1; i <= n; i++) l[cnt = i - n + k] = i;
		for(int i = 1; i <= n; i++)
			if(sa[i] > k) l[++cnt] = sa[i] - k;
		// 按第一關鍵字排序 
		for(int i = 1; i <= m; i++) t[i] = 0;
		for(int i = 1; i <= n; i++) ++t[rk[i]];
		for(int i = 1; i <= m; i++) t[i] += t[i - 1];
		for(int i = n; i >= 1; i--) sa[t[rk[l[i]]]--] = l[i], l[i] = 0;
		// 處理新的 rk 
		swap(rk, l), cnt = 0;
		for(int i = 1; i <= n; i++)
			if(l[sa[i]] == l[sa[i - 1]] && l[sa[i] + k] == l[sa[i - 1] + k]) rk[sa[i]] = cnt;
			else rk[sa[i]] = ++cnt;
		if((m = cnt) == n) break;
	}
	// 處理最終的 rk 
	for(int i = 1; i <= n; i++) rk[sa[i]] = i;
}
void getHeight() {
	for(int i = 1, k = 0; i <= n; i++) {
		if(rk[i] == 1) continue;
		if(k) --k;
		for(int j = sa[rk[i] - 1]; j + k <= n && i + k <= n && c[i + k] == c[j + k]; ++k);
		height[rk[i]] = k;
	}
}

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr);
	scanf("%s", c + 1);
	n = strlen(c + 1);
	buildSA();
	getHeight();
	for(int i = 1; i <= n; i++) cout << sa[i] << ' ';

	return 0;
}