SA学习笔记
参考资料
字符串基础
自动机相关
这两个都是 Alex_wei 大神的。
SA 好文,有很多应用例子
SAM 这篇以应用为主
hjh 26 C班课件
还有 26 B班字符串笔记
SA
概述
将给定字符串的每一个后缀按照字典序排序,运用倍增可以做到 \(O(n\log n)\) 求出,比较重要的是 \(height\) 数组。
一些符号
\(rk[i]\) 表示起点下标为 \(i\) 的后缀的排名,\(sa[i]\) 表示排名为 \(i\) 的后缀对应的起点下标。
\(height[i]\) 一般简记为 \(ht[i]\),定义:
\[ht[i]=lcp(sa[i],sa[i-1])
\]
即排名相邻的两个后缀的最长公共前缀。
一些性质
-
\(sa\) 和 \(rk\) 互为逆排列,于是有 \(sa[rk[i]]=i,rk[sa[i]]=i\),注意这两个式子的 \(i\) 意义有所不同,第一个 \(i\) 是下标,第二个是排名。
-
\(\forall i<j, \ \ lcp(sa[i],sa[j])=\min_{i<k\le j} ht[k]\)。这是容易看出的。
-
\(ht[rk[i]]\ge ht[rk[i-1]]-1\),这个性质可以帮助线性求出 \(ht\) 数组。
证明
\[\begin{align*} ht[rk[i-1]]&=lcp(sa[rk[i-1]],sa[rk[i-1]-1])\\&=lcp(i-1,sa[rk[i-1]-1]) \end{align*} \]而 \(i\) 是 \(i-1\) 的一个后缀,所以 \(lcp(i,sa[rk[i-1]-1])\ge ht[rk[i-1]]-1\)。
如何实现
模板题
模板代码
const int N=1e6+5;
int n,buc[N],tmp[N],rk[N<<1],sa[N],v=256; //v是rk值域
//tmp是按第二关键字排序(不用管第一个)(存的是第一关键字的位置),也充当辅助更新rk数组的作用。
string s;
void sortsa(){
memset(buc,0,sizeof(int)*(v+3));
for(int i=1;i<=n;i++) buc[rk[i]]++;
for(int i=2;i<=v;i++) buc[i]+=buc[i-1];
// for(int i=97;i<=v;i++) cout<<buc[i]<<" \n"[i==v];
for(int i=n;i>=1;i--) sa[buc[rk[tmp[i]]]--]=tmp[i];
}
signed main(){
// system("fc .out .out");
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
IOS
cin>>s;
n=s.length();
s=" "+s;
for(int i=n;i>=1;i--) rk[i]=s[i],tmp[i]=i;
// for(int i=1;i<=n;i++) cout<<rk[i]<<" \n"[i==n];
//初始rk按字典序排序,tmp第二关键字为0,存的就是自己
sortsa();
// for(int i=1;i<=n;i++) cout<<sa[i]<<" \n"[i==n];
for(int k=1;k<=n;k<<=1){
int tot=0;
for(int i=n-k+1;i<=n;i++) tmp[++tot]=i; //第二关键字为0的
for(int i=1;i<=n;i++) if(sa[i]-k>0) tmp[++tot]=sa[i]-k; //按第二关键字排序
sortsa(); //得到新的sa数组
tmp[sa[1]]=v=1; //这里tmp作为新的rk,v也重置。
for(int i=2;i<=n;i++) { //由于这里 len 还是旧的 k-1 长,所以这里要用旧的 rk
if(rk[sa[i]]^rk[sa[i-1]] || rk[sa[i]+k]^rk[sa[i-1]+k]) v++; //这里rk可能会越界,开2倍
tmp[sa[i]]=v;
}
memcpy(rk+1,tmp+1,sizeof(int)*(n+3));
}
for(int i=1;i<=n;i++) cout<<sa[i]<<" ";
return 0;
}
线性求 ht 数组
code
for(int i=1,len=0;i<=n;i++){
if(rk[i]==1) {
h[1]=len=0;
continue;
}
if(len) len--; //继承
int j=sa[rk[i]-1]; //前一个位置
while(i+len<=n && j+len<=n && s[i+len]==s[j+len]) len++;//暴力扩展
h[rk[i]]=len;
}
应用
看打粗体的那行的参考资料
注意事项
- \(buc\) 桶的值域是关于 \(rk\) 的,也就是 \(v\)。

浙公网安备 33010602011771号