后缀树组 学习笔记

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

以上是关于后缀树组 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

后缀树组(SA)初探

K-th occurrence(后缀树组+划分树+ST表+RMQ+二分)

CF504E Misha and LCP on Tree(树链剖分+后缀树组)

学习笔记:python3,代码片段(2017)

学习笔记:后缀数组

[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段