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]) \]

即排名相邻的两个后缀的最长公共前缀。

一些性质

  1. \(sa\)\(rk\) 互为逆排列,于是有 \(sa[rk[i]]=i,rk[sa[i]]=i\),注意这两个式子的 \(i\) 意义有所不同,第一个 \(i\) 是下标,第二个是排名。

  2. \(\forall i<j, \ \ lcp(sa[i],sa[j])=\min_{i<k\le j} ht[k]\)。这是容易看出的。

  3. \(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;
}

应用

看打粗体的那行的参考资料

注意事项

  1. \(buc\) 桶的值域是关于 \(rk\) 的,也就是 \(v\)

题目

posted @ 2026-08-08 00:27  lcx_OIer  阅读(6)  评论(0)    收藏  举报