后缀数组笔记
进口后缀数组笔记
为了简化思维,我们直接上定义。
注释 : 本文中所有“位置”指原串位置,“排名”指每个后缀进行排序后的排名。
\(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\) 对\(i\leq 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;
}
}