字尾樹組 學習筆記
阿新 • • 發佈:2021-06-24
0xFF 一些備註
本篇部落格所有證明基本略過,主要總結字尾樹組的應用
引用有 [2009]字尾陣列——處理字串的有力工具 by. 羅穗騫、OI wiki、日報 的大量內容
實現方面本篇部落格只會了倍增方法
0x00 一些定義
-
第 \(i\) 個字尾指的是首字元在 \(i\) 位置的字尾
-
這裡排序的關鍵字是字典序,定義空字元最小
-
\(sa[i]\) 表示排名為 \(i\) 的字尾是第幾個字尾
-
\(rk[i]\) 表示第 \(i\) 個字尾的排名是多少
-
\(lcp(i,j)\) 表示字尾 \(i\) 和字尾 \(j\) 的最長公共字首長度
-
\(height[i]\) 表示 \(lcp(sa[i],sa[i-1])\)
0x01 字尾排序
目標:將一個字串的 \(n\) 個字尾按照字典序大小排序
實現:按照倍增的思想,分別排序前 \(2^0, 2^1, 2^2, 2^3……\) 個字元。上一次排序過後,以上一次排序為第二關鍵字,當前層為第一關鍵字繼續排序,總複雜度 \(O(nlogn)\)
至於怎樣 \(O(n)\) 排序,可以採用基數排序,因為上一層可以順便幫助這一層離散化
具體來講,開 \(n\) 個桶,將值放進去,然後自然而然就拍好序了
直接放程式碼,照著程式碼來具體講:
void get_sa(){ for(int i=1;i<=n;i++)c[x[i]=s[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++){ if(sa[i]>k)y[++num]=sa[i]-k; } for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)c[x[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==n)break; m=num; } return ; } int main(){ m=122; scanf("%s",s+1); n=strlen(s+1); get_sa(); }
和 \(c[i]\) 陣列有關的是基數排序的過程,不再贅述
for(int i=n-k+1;i<=n;i++)y[++num]=i;
for(int i=1;i<=n;i++){
if(sa[i]>k)y[++num]=sa[i]-k;
}
這兩行的意思是這樣的,由於從 \(n-k+i\) 開始,由於長度不夠,所以子串為空,自然最短,而剩下的則有 \(sa[i]\) 決定,這樣省去了第二關鍵字排序的過程
lcp問題的求解
直接放一些我也不會證的結論:
\(∀1≤i< j < k \le n, lcp(sa_i,sa_k)=min \{lcp(sa_i,sa_j),lcp(sa_j,sa_k) \}\)