模板:后缀数组(SA)
文章目錄
- 前言
- 解析
- 后綴排序
- 優化1:基數排序
- 優化2:簡化第一次排序
- 優化3:提前break
- 完整代碼
- LCP與height
所謂后綴數組,就是存儲后綴的數組
(逃)
前言
為什么一個算法,如此難以理解卻依然是成為一個成熟OIer不可回避的必修課?
足以可見后綴家族功能的強大
首先,由于其本身的性質,后綴數組對字典序相關的問題十分擅長
同時,由于 heightheightheight 數組的眾多優秀性質,它在處理公共串問題和 LCP 問題上也十分強大
(我目前SA的題加起來也沒做上十道,所以這樣的“總結”請選擇性閱讀)
解析
后綴排序
P3809 【模板】后綴排序
給出一個字符串,把所有后綴按照字典序排序
n≤106n\le10^6n≤106
考慮倍增
一開始子串長度為 111,每個位置的排名 rkirk_irki? 就是自己位置的字符
然后在已知長度為 www 的所有子串的排名的情況下,以 rki+wrk_{i+w}rki+w? 為第二關鍵字,rkirk_irki? 為第一關鍵字排序,可以得到長度為 2w2w2w 的所有子串的排名(空串的排名視為負無窮)
每次用 sort 的話,時間復雜度 O(nlog2n)O(nlog^2n^)O(nlog2n)
優化1:基數排序
注意到這里的排序是關于大小的排序,且值域(排名)只有 O(n)O(n)O(n)
所以我們可以使用基數排序代替 sort,時間復雜度變成 O(nlogn)O(nlogn)O(nlogn)
注意! 基數排序重新排列的循環必須倒序枚舉,這樣才能保證排序的穩定性
memset(cnt,0,sizeof(cnt)); memcpy(oldrk,rk,sizeof(rk)); for(int i=1;i<=n;i++) ++cnt[rk[id[i]]]; for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1]; for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i]; p=0; for(int i=1;i<=n;i++){if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=p;else rk[sa[i]]=++p; } m=p;優化2:簡化第一次排序
第一次是關于rkiwrk_{i_w}rkiw?? 排序
并不需要基數排序,只需要:
即可
優化3:提前break
玄學優化
大概就是,不必真的倍增到總長度,只需要讓所有字符串的排名互相分開即可
這東西在全是 a 這樣的串中可以說是等于沒有,但在不少時候優化巨大(比如本題 2.2s→0.8s2.2s\to 0.8s2.2s→0.8s)
完整代碼
saisa_isai?:排名為 iii 的后綴的編號
rkirk_irki?:后綴 iii 的排名
LCP與height
定義:
height(i)height(i)height(i) 表示后綴 saisa_isai? 和后綴 sai?1sa_{i-1}sai?1? 的最長公共前綴(lcp(sai,sai?1)lcp(sa_i,sa_{i-1})lcp(sai?,sai?1?))。特別的,lcp(1)=0lcp(1)=0lcp(1)=0
感性理解來說,把所有后綴按照字典序排序后,height(i)height(i)height(i) 就是相鄰兩個后綴的相同部分的長度。
引理1:lcp(i,j)=min?(lcpi,k,lcpk,j)lcp(i,j)=\min (lcp_{i,k},lcp_{k,j})lcp(i,j)=min(lcpi,k?,lcpk,j?),對于任意的 i≤k≤ji\le k\le ji≤k≤j 均成立.
證明:
首先,min?(lcpi,k,lcpk,j)\min (lcp_{i,k},lcp_{k,j})min(lcpi,k?,lcpk,j?) 是 kkk 與 i,ji,ji,j 共同的公共前綴,所以也必然是 i,ji,ji,j 的公共前綴,lcp(i,j)≥min?(lcpi,k,lcpk,j)lcp(i,j)\ge\min (lcp_{i,k},lcp_{k,j})lcp(i,j)≥min(lcpi,k?,lcpk,j?)。
同時,由于字典序單調的性質,iii 變到 kkk 變化的前綴在 kkk 變化到 jjj 時必然不可能再變回來,否則 jjj 的字典序就比 kkk 小了,所以有 lcp(i,j)≤min?(lcpi,k,lcpk,j)lcp(i,j)\le\min (lcp_{i,k},lcp_{k,j})lcp(i,j)≤min(lcpi,k?,lcpk,j?)。
綜上,lcp(i,j)=min?(lcpi,k,lcpk,j)lcp(i,j)=\min (lcp_{i,k},lcp_{k,j})lcp(i,j)=min(lcpi,k?,lcpk,j?),證畢。
引理2:heightrki≥heightrki?1?1height_{rk_i}\ge height_{rk_{i-1}}-1heightrki??≥heightrki?1???1
證明:
rki?1≤1rk_{i-1}\le1rki?1?≤1 時,顯然成立
rki?1>1rk_{i-1}>1rki?1?>1 時,設 rki?1?1=krk_{i-1}-1=krki?1??1=k(kkk 就是 i?1i-1i?1 按字典序排序后的前一個),那么:
若 i?1i-1i?1 和 kkk 的首字母不同, hi?1=0h_{i-1}=0hi?1?=0 ,顯然成立
若 i?1i-1i?1 和 kkk 的首字母相同,那么考慮字符串 k+1k+1k+1,由于k 去掉首字符變成 k+1,i-1 去掉首字母變成 i,所以 k+1k+1k+1 也一定在 iii 的前面,同時 lcp(k+1,i)=lcp(k,i?1)?1=heightrki?1?1lcp(k+1,i)=lcp(k,i-1)-1=height_{rk{i-1}}-1lcp(k+1,i)=lcp(k,i?1)?1=heightrki?1??1,由引理1,有 lcp(k+1,i)=min?(lcp(k+1,rki?1),lcp(i?1,i))lcp(k+1,i)=\min (lcp(k+1,rk_i-1),lcp(i-1,i))lcp(k+1,i)=min(lcp(k+1,rki??1),lcp(i?1,i)),故 lcp(i?1,i)≥heightrki?1?1lcp(i-1,i)\ge height_{rk{i-1}}-1lcp(i?1,i)≥heightrki?1??1,即 heightrki≥heightrki?1?1height_{rk_i}\ge height_{rk_{i-1}}-1heightrki??≥heightrki?1???1
得證。
得出這個性質后,線性求 heightheightheight 的代碼就不難寫出了:
Thanks for reading!
總結
以上是生活随笔為你收集整理的模板:后缀数组(SA)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2025 款梅赛德斯-AMG CLE 5
- 下一篇: CodeForces:1103(div1