@(學習筆記)[后缀数组]
一些基本的定義
LCP: Longest Common Prifix最长公共前缀
后缀数组(sa[i]): 表示排名为(i)的后缀的起始坐標.
名次數組(rank[i]): 表示以(i)為起始的後綴的排名
*Hint: 上述的"排名"是指字符串從前往後的按照字典序的排名
簡單來說, 就是: 後綴數組(sa[i])表示"排名第幾的是誰", 名次數組(rank[i])表示"你排第幾". 值得注意的是, 即便是對於相同的後綴串, sa也必然是不同的, 而對應後綴相同的rank值則是相同的(詳見代碼, 這樣的原因是能方便後續處理).
這裡就有了一些性質:
- (LCP(i, j) = LCP(j, i))
- (LCP(i, i) = strlen(suffix(sa[i])) = n - sa[i] + 1)
height數組 & h數組:
height數組定義: $$height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀(LCP)$$也就是說, (height[i])表示排名相鄰的兩個後綴的最長公共前綴.
對於(j < k), 不妨設(rank[j] < rank[k]), 則有性質: suffix(j)和suffix(k)的最长公共前缀为(height[rank[j]+1], height[rank[j]+2], height[rank[j]+3], .., height[rank[k]])中的最小值(原因稍加思考即可得到, 在此不作過多說明).
(h[i]): 表示(suffix(i))與排名前一位的LCP的值. 即: $$h[i] = height[rank[i]]$$. 則(h[i])有性質: $$h[i] ge h[i - 1] - 1$$
然而: 在代碼實現時我們通常並不需要儲存h數組..
從某種程度上說, 後綴數組存在的意義就是為了計算height數組和h數組的值.
實現
這裡的是倍增的做法.
#include<cstdio>
#include<cctype>
#include<cstring>
using namespace std;
inline char* read(char *s)
{
char c;
while(! isgraph(c = getchar()));
int len = 0;
while(isgraph(c))
s[len ++] = c, c = getchar();
return s;
}
const int L = 1 << 10;
char s[L];
int lenS;
int sum[L];
int sa[L], rank[L], tmpSa[L], tmpRank[L];
void getSuffixArray()
{
memset(sa, - 1, sizeof(sa));
memset(rank, - 1, sizeof(rank));
memset(tmpSa, - 1, sizeof(tmpSa));
memset(tmpRank, - 1, sizeof(tmpRank));
memset(sum, 0, sizeof(sum)); //初始化
//此时排序只关注单个字符,所以每个起点为i长度为1的串的排名就是s[i]本身
for(int i = 0; i < lenS; i ++)
sum[s[i]] ++;
for(int i = 1; i < LIM; i ++) //注意訪問不要越界
sum[i] += sum[i - 1];
for(int i = lenS - 1; ~ i; i --)
sa[-- sum[s[i]]] = i;
//更新rank數組
rank[sa[0]] = 0;
int p = 0;
for(int i = 1; i < lenS; i ++)
rank[sa[i]] = s[sa[i]] != s[sa[i - 1]] ? ++p : p;
int lim = p + 1;
//開始倍增
for(int i = 1; lim < lenS; i <<= 1)
{
//對於每一次倍增, 維護的後綴串的長度為i * 2
//下面一段是基數排序後綴串后i位的過程
int p = 0;
for(int j = lenS - i; j < lenS; j ++)
tmpSa[p ++] = j;
//上一句: 假如后i位直接為空的話, 排到最前面
//下一句: 假如不為空的話, 根據上一次的結果來排
for(int j = 0; j < lenS; j ++)
if(sa[j] >= i)
tmpSa[p ++] = sa[j] - i;
//下面一段: 初始化, 接著要把后i位的排序與前i位合併
memset(sum, 0, sizeof(sum));
//這一段就是把后i位與前i位合併的過程
for(int j = 0; j < lenS; j ++)
sum[rank[j]] ++;
//記錄每一種權值的數量
for(int j = 1; j < lim; j ++)
sum[j] += sum[j - 1];
//求出前綴和
for(int j = lenS - 1; ~ j; j --)
sa[-- sum[rank[tmpSa[j]]]] = tmpSa[j];
//求解sa數組
//下面一段: 更新rank數組
memcpy(tmpRank, rank, sizeof(rank)); //tmpRank只是一個臨時空間, 用於備份
p = 0;
rank[sa[0]] = p;
for(int j = 1; j < lenS; j ++)
{
if(tmpRank[sa[j]] != tmpRank[sa[j - 1]] || tmpRank[sa[j] + i] != tmpRank[sa[j - 1] + i])
p ++;
rank[sa[j]] = p;
}
lim = p + 1;
}
}
int height[L];
//根據sa數組求出height值
//這一段要背下來
void getHeight()
{
int p = 0;
for(int i = 0; i < lenS; i ++)
if(rank[i])
{
if(p)
p --;
while(s[i + p] == s[sa[rank[i] - 1] + p])
p ++;
height[rank[i]] = p;
}
}
int main()
{
read(s);
lenS = strlen(s);
getSuffixArray();
getHeight();
}