基于基数排序的倍增实现后缀数组
后缀树组的基本做法是倍增,时间复杂度是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)块内查询。