芝士:字尾陣列(SA)
阿新 • • 發佈:2020-12-19
例子
基數排序
它的工作原理是將待排序的元素拆分為k個關鍵字(比較兩個元素時,先比較第一關鍵字,如果相同再比較第二關鍵字……),然後先對第k關鍵字進行穩定排序,再對第k-1關鍵字進行穩定排序,再對第k-2關鍵字進行穩定排序……最後對第一關鍵字進行穩定排序,這樣就完成了對整個待排序序列的穩定排序。
--OI WIKI
過程
pace 1
考慮對兩個字串進行比較
定義其為\(s1,s2\)
如果\([0,i]\)已經能比較出大小,那麼就不需要考慮\([i+1,lens)\)的關係了
反之,如果\([0,i]\)不能比較出大小,那麼就需要考慮\([i+1,lens)\)
pace 2
定義位置\(i\)的字串為\([i,lens)\)的字元
比如\(abaaba\)
\[0:abaaba\\1:baaba\\2:aaba\\3:aba\\4:ba\\5:a\\ \]考慮對於所有位置\(i\)字串按第一位進行排序
那麼排完序就會有
\[abaaba\\aaba\\aba\\a\\baaba\\ba \]現在單獨考慮第二位的大小,考慮到字尾之間的聯絡,現在真的需要知道每一個字尾具體是什麼樣子麼?
位置\(0\)的字串的第二位不就是位置\(1\)的字串的第一位?
那麼對於每一個位置都可以找到一個第一位與之第二位相對應
那麼我們就可以知道只考慮第二位的字尾之間的大小關係
然後我們知道第二位的大小關係,又知道第一位的大小關係、
那麼運用基數排序的思想,就可以知道前兩位,即\([0,1]\)的大小關係
pace 3
按照pace2的內容進行遞推,
如果我們知道只考慮前k位的大小關係,即\([0,k-1]\)的大小關係
現在我們想求出只考慮\([0,2k-1]\)的大小關係
考慮對於位置\(i\)的字串,其\([k,2k-1]\)的字元就是位置\(i+k\)的前\(k\)位
故可以知道只考慮\([k,2k-1]\)的大小關係
那麼按照基數排序,就可以有\([0,2k-1]\)的大小關係
程式碼
#include<iostream> #include<cstring> using namespace std; char s[1000005]; int n; int m; int sa[1000005]; int rank1[1000005]; int rank2[1000005]; int t_rank[1000005]; int solve_hash(char c) { if('0'<=c&&c<='9') return c-'0'+1; if('A'<=c&&c<='Z') return c-'A'+11; return c-'a'+37; } void init() { for(int i=1;i<=n;i++) { rank1[i]=solve_hash(s[i]); rank2[i]=i; } } void _sort() { for(int i=1;i<=m;i++) t_rank[i]=0; for(int i=1;i<=n;i++) t_rank[rank1[i]]++; for(int i=1;i<=m;i++) t_rank[i]+=t_rank[i-1]; for(int i=n;i>=1;i--) { sa[t_rank[rank1[rank2[i]]]]=rank2[i]; t_rank[rank1[rank2[i]]]--; } } void get_sa() { for(int w=1,p=0;p<n&&w<=n;m=p,p=0,w<<=1) { for(int i=n-w+1;i<=n;i++) rank2[++p]=i; for(int i=1;i<=n;i++) { if(sa[i]>w) { rank2[++p]=sa[i]-w; } } _sort(); swap(rank1,rank2); p=rank1[sa[1]]=1; for(int i=2;i<=n;i++) if(rank2[sa[i]]==rank2[sa[i-1]]&&rank2[sa[i]+w]==rank2[sa[i-1]+w]) rank1[sa[i]]=p; else rank1[sa[i]]=++p; } } int main() { ios::sync_with_stdio(false); cin>>(s+1); n=strlen((s+1)); m=62; init(); _sort(); get_sa(); for(int i=1;i<=n;i++) cout<<sa[i]<<' '; return 0; }
字尾的LCT
咕咕咕