后缀数组笔记

进口后缀数组笔记

为了简化思维,我们直接上定义。

注释 : 本文中所有“位置”指原串位置,“排名”指每个后缀进行排序后的排名。

(sa[i];;;) 排名为i的后缀的位置

(rk[i];;;) 位置为i的后缀的排名

(LCP(i,j);;;) 位置为(sa[i])(sa[j])的后缀的(LCP)

(LCP; Lemma) 对于(forall i leq j le k , LCP(i,k) leq LCP(j,k)),排名相隔较远的肯定没有较近的有更多的前缀相同字符

(LCP; Theorem) (LCP(i,j)=min { LCP_{k=i+1}^{j}(k-1,k) })两个排名为(i,j)的字符串LCP为他们之间的相邻LCP取最小值

(LCP; Corollary)(ileq j < k, LCP(i,k)leq LCP(j,k))

(height[i]) (LCP(i-1,i))(height[1]=0),即排名为(i-1)与排名为(i)的后缀的(LCP)大小

(h[i]) (height[rk[i]])位置为(i)的后缀同它排名上前一个串的(LCP)大小


性质:对于(i>1)(rk[i]>1),一定有(h[i]geq h[i-1]-1)

前置:若(LCP(rk[i],rk[j])>1),当且仅当(Leftrightarrow LCP(str[i,n],str[j,n]))

给个代码注释:

const int S=200003;
int T,n,c[S*2],sa[S*2],x[S*2],y[S*2],h[S*2],rk[S*2],res,m;//不开两倍串长的话第二关键字会越界访问
char a[S];
void get_sa()
{
	memset(c,0,sizeof(c));
    //这一部分是预处理。讲白了,就是第二关键字均为空串的第一二关键字的混合排序。
	for (int i=1;i<=n;i++)
		c[x[i]=a[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;//倒着做,这样相同字符,原序列较后的排名较后。除了x中的字符,其他的桶值显然不必管它。显然不可以正着做。
	for (int o=1;o<=n;o<<=1)//倍增第一关键字的长度,第二关键字长度和第一关键字相等。
	{
        //根据基数排序的原理,我们先排第二关键字,再排第一关键字。
		int top=0;
		for (int i=n-o+1;i<=n;i++)//枚举后面这些第二关键字空串的一字位置。
			y[++top]=i;//第二关键字显然是后面的空串们最靠前。
		for (int i=1;i<=n;i++)//枚举非空串第二关键字的开头排名,按照上一次的第一关键字排完序的答案就行。
			if (sa[i]>o)//由于前o个已经确定了是o个空串,所以非空串(长度不一定满o)不可以是原串中前o个字符,他们无法构成第二关键字。
				y[++top]=sa[i]-o;
		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];//原来的i替换为了y[i],因为原来都是空串,所以序是原串序。一样从后到前枚举。
		for (int i=1;i<=n;i++)
			y[i]=x[i];//就是拷贝一下数组,也可以从重新开一个数组
		x[sa[1]]=1;top=1;//重新把排名为1的位置的值改为1
		for (int i=1;i<=n;i++)
			x[sa[i]]=(y[sa[i]]!=y[sa[i-1]] || y[sa[i]+o]!=y[sa[i-1]+o])+x[sa[i-1]];//然后算每一位的新值。y为基数排序结束后的老的x,x为下一轮要的新的x。
		m=x[sa[n]];
		if (m==n) break;//本来后缀排序后就是不会有并列的。这两句表明,如果这一轮搞定了排名,那么不必再往下比了。
	}
}
void get_height(char a[],int height[],int rk[],int sa[])
{
	int k=0;
	for (int i=1;i<=n;++i) rk[sa[i]]=i;//根据定义搞出rk
	for (int i=1;i<=n;++i)
	{
		if (rk[i]==1) continue;根据定义啦
		if (k) --k;//k代表h[i-1],height[rk[i-1]],我们有h[i]>=h[i-1]-1
		int j=sa[rk[i]-1];//为了搞出这个h[i],我们找到排名上一个的位置
		while (i+k<=n && j+k<=n && a[i+k]==a[j+k]) ++k;//然后暴力找。根据h[i]>=h[i-1]-1,总时间复杂度O(N)。
		height[rk[i]]=k;
	}
}
原文地址:https://www.cnblogs.com/Algebra-hy/p/13082712.html