sa后缀数组7.28

SA后缀数组

【模板】后缀数组

理解全在代码里

定义 :

\(sa[i]\) 表示排名为i的后缀的开头位置

\(rk[i]\) 表示以i为开头的后缀的排名(这里\(i\)均表示下标)

\(tmp[i]\) 表示i对应的i-w的位置,为后面倍增两两合并准备

\(height[i]\) 表示\(lcp{({sa[i]},{sa[i - 1]})}\)

\(cnt[i]\) 计数排序的计数器



void get_sa(){
    int m = 255 ;
    for(int i = 1 ; i <= n ;++i)cnt[rnk[i] = s[i]]++; //初始化,给长度为1的赋值
    for(int i = 1 ; i <= m ;++i)cnt[i] += cnt[i - 1] ;//前缀和,计算
    for(int i = n ; i >= 1 ;--i)sa[cnt[rnk[i]]--] = i ; 
    for(int w = 1 , t = 0 ; ; m = t , t = 0 , w <<= 1 ){//每次倍增长度
        for(int i = n - w + 1 ; i <= n ;++i)tmp[++t] = i;//如果 i + w > 0 就把他平行移到左边
        for(int i = 1 ; i <= n ;++i)
            if( sa[i] > w )tmp[++t] = sa[i] - w ;//否则直接赋值
        // for(int i = 1 ; i <= n ; i++){
        //     cout << i << " " << tmp[i] << " " << w << endl ;  
        // } 
        // cout << endl ; 
        for(int i = 0 ; i <= m ;++i)cnt[i] = 0 ;//重置计数器
        for(int i = 1 ; i <= n ;++i)cnt[rnk[tmp[i]]]++;//对第一关键字进行计数排序
        for(int i = 1 ; i <= m ;++i)cnt[i] += cnt[i - 1];//前缀和
        for(int i = n ; i >= 1 ;--i)sa[cnt[rnk[tmp[i]]]--]= tmp[i];//计算sa  
        swap(rnk , tmp) , t = 0 ;//这里应该是节省空间 直接使用把老的rnk赋给tmp , 用老的rnk更新新的rnk
        for(int i = 1 ; i <= n ; i++)
            rnk[sa[i]] = (tmp[sa[i]] == tmp[sa[i - 1]] and tmp[sa[i] + w] == tmp[sa[i - 1] + w] ) ? t : ++t ; 
        if( t == n )break ;//如果已经够了就不再排序了
    }
}
void getHeight(){
    for(int i = 1 , k = 0 ; i <= n ; ++i){
        if(k)--k;
        while(s[i + k] == s[sa[rnk[i] - 1] + k])k++;//直接暴力扩展
        height[rnk[i]] = k ;
    }
}

getHeight函数复杂度均摊\(O(n)\)

get_sa函数复杂度\(O(n{logn})\)

应用:

1.比较两个子串的大小关系

2.不同子串的数目

不同子串个数有 :\(\\{n*(n+1)/2 + sum_{i=1}height[i]}\)

posted @ 2025-07-28 07:50  Nailong2357  阅读(15)  评论(1)    收藏  举报