后缀树组 学习笔记
Posted y_cx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后缀树组 学习笔记相关的知识,希望对你有一定的参考价值。
0xFF 一些备注
本篇博客所有证明基本略过,主要总结后缀树组的应用
引用有 [2009]后缀数组——处理字符串的有力工具 by. 罗穗骞、OI wiki、日报 的大量内容
实现方面本篇博客只会了倍增方法
0x00 一些定义
-
第 \\(i\\) 个后缀指的是首字符在 \\(i\\) 位置的后缀
-
这里排序的关键字是字典序,定义空字符最小
-
\\(sa[i]\\) 表示排名为 \\(i\\) 的后缀是第几个后缀
-
\\(rk[i]\\) 表示第 \\(i\\) 个后缀的排名是多少
-
\\(lcp(i,j)\\) 表示后缀 \\(i\\) 和后缀 \\(j\\) 的最长公共前缀长度
-
\\(height[i]\\) 表示 \\(lcp(sa[i],sa[i-1])\\)
0x01 后缀排序
目标:将一个字符串的 \\(n\\) 个后缀按照字典序大小排序
实现:按照倍增的思想,分别排序前 \\(2^0, 2^1, 2^2, 2^3……\\) 个字符。上一次排序过后,以上一次排序为第二关键字,当前层为第一关键字继续排序,总复杂度 \\(O(nlogn)\\)
至于怎样 \\(O(n)\\) 排序,可以采用基数排序,因为上一层可以顺便帮助这一层离散化
具体来讲,开 \\(n\\) 个桶,将值放进去,然后自然而然就拍好序了
直接放代码,照着代码来具体讲:
void get_sa(){
for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1){
int num=0;
for(int i=n-k+1;i<=n;i++)y[++num]=i;
for(int i=1;i<=n;i++){
if(sa[i]>k)y[++num]=sa[i]-k;
}
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;
num=1;
for(int i=2;i<=n;i++){
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
}
if(num==n)break;
m=num;
}
return ;
}
int main(){
m=122;
scanf("%s",s+1);
n=strlen(s+1);
get_sa();
}
和 \\(c[i]\\) 数组有关的是基数排序的过程,不再赘述
for(int i=n-k+1;i<=n;i++)y[++num]=i;
for(int i=1;i<=n;i++){
if(sa[i]>k)y[++num]=sa[i]-k;
}
这两行的意思是这样的,由于从 \\(n-k+i\\) 开始,由于长度不够,所以子串为空,自然最短,而剩下的则有 \\(sa[i]\\) 决定,这样省去了第二关键字排序的过程
lcp问题的求解
直接放一些我也不会证的结论:
\\(∀1≤i< j < k \\le n, lcp(sa_i,sa_k)=min \\{lcp(sa_i,sa_j),lcp(sa_j,sa_k) \\}\\)
以上是关于后缀树组 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
K-th occurrence(后缀树组+划分树+ST表+RMQ+二分)
CF504E Misha and LCP on Tree(树链剖分+后缀树组)
[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段