【更新中】后缀数组学习笔记

基于基数排序的倍增实现后缀数组

后缀树组的基本做法是倍增,时间复杂度是O(nlogn),代码简短,思想巧妙。最妙的东西是基数排序,以及倍增字符串的比较单位字符串长度翻倍。
当字符集很大的时候(1e9),后缀树组照样运行,而后缀自动机就很难搞,建边需要哈希。
c++11的比较函数:sort(p, p+n, [&](const int &a, const int &b));

int n, alpha=256, bkt[maxn], rk[maxn], sa[maxn], SA[maxn], RK[maxn];
char s[maxn];
int main(){
    scanf("%s", s+1); n=strlen(s+1);
    for(int i=1;i<=n;++i) bkt[s[i]]++;
    for(int i=1;i<=alpha;++i) bkt[i]+=bkt[i-1];
    for(int i=1;i<=n;++i) sa[bkt[s[i]]--]=i;
    for(int i=1;i<=n;++i) rk[sa[i]]=rk[sa[i-1]]+(s[sa[i-1]]!=s[sa[i]]);
    for(int p=1;p<=n;p<<=1){
        for(int i=1;i<=n;++i) bkt[sa[i]]=i;
        for(int i=n;i>=1;--i) if(sa[i]>p) SA[bkt[rk[sa[i]-p]]--]=sa[i]-p;
        for(int i=n;i>n-p;--i) SA[bkt[rk[i]]--]=i;
        #define cmp(x,y,p)  (rk[x]!=rk[y]||rk[x+p]!=rk[y+p])
        for(int i=1;i<=n;++i) RK[SA[i]]=RK[SA[i-1]]+cmp(SA[i],SA[i-1],p);
        for(int i=1;i<=n;++i) rk[i]=RK[i], sa[i]=SA[i];
        if(rk[sa[n]]>=n) break;
    }
}

有关lcp

令 lcp(i,j) 表示排名为 i 和排名为 j 的后缀的最长公共前缀的长度。即 sa[i] 和 sa[j] 的最长公共前缀的长度。
那么有:
定理一:\(lcp(l,r)=min(lcp(l,i),lcp(i,r))\),其中 i 是 [l,r] 中任意一个数。
定理二:\(lcp(l,r)=min_{i=l+1}^{r}(lcp(i-1,i))\)

有关height数组和h数组

height[i]表示排名为i的后缀与排名为i+1的后缀的lcp。
h[i]=height[rnk[i]],表示下标为i的后缀与这个后缀排名下一个的后缀的lcp。
h[sa[i]]=height[i],表示排名为i的后缀与排名为i+1的后缀的lcp。
考虑根据后缀数组线性求h[i]。有:

\(h[rnk[i]] \leq h[rnk[i]+1]]+1\)

后缀数组的好兄弟——调和级数

待更

练习1:求本质不同的子串

总的子串个数减去重复的子串个数,\(n*(n-1)/2-\sum_i h[i]\)

练习2:CF1073G Yet Another LCP Problem

虚树,单调栈

练习3:区间本质不同的子串

是SAM和LCT的板子题

练习4:

定义区间匹配:区间长度一样;区间不交;$ h_l_{1 + i} + h_l_{2 + i} = h_l_1 + h_l_2 $。
给定区间,询问源字符串里有多少区间与之匹配

$ h_l_{1+i}-h_l_1 = h_l_2 - h_l_{2+i} $ 只和相对大小有关,故差分之。

$ h_i^'=h_{i+1}-h_i $ ,匹配即相反数。

根号平衡

虽然左右端点的变动是nlogn,但是询问只有n。
使得修改为O(1),查询复杂度为O(n)。
每个块内开桶维护全局复杂度。询问的零散点暴力O(n)块内查询。

posted @ 2021-11-01 21:02  _Buffett  阅读(40)  评论(0编辑  收藏  举报