简单入门
sa[i]表示将后缀从小到大排名后,第i小的后缀的位置
rank[i]表示i这个后缀的排名
height[i]表示sa[i]和sa[i-1]的lcp
lcp(i,j)表示sa[i]和sa[j]的lcp。则lcp(i,j)=min(height[i+1],……,height[j]).
求后缀数组的原理:
将每次我们求的后缀的长度*2,求这个长度的后缀的排名。则求这次的后缀的排名时可以使用以前的信息。如求位置为i,长度为j*2的后缀的排名,则可以通过比较位置i,长度为j的后缀(第一关键字)和位置i+j,长度为j的后缀(第二关键字)的排名(上一次长度的信息)来得到i位置长度为j的后缀的排名。
求后缀数组的方法:
那么如果求每个后缀时都直接保存上次的信息,然后快排,则效率为nlog^2n.使用基数排序的技巧来优化,使得效率达到nlogn。
记w[i]表示第一关键字排名为i的后缀数量,然后做一遍前缀和的值。即所有第一关键字为i时第二关键字最大时的后缀的排名。
x[i]表示上一轮排名后,i的排名。
暂时的rank[i]表示第二关键字排名第i小的第一关键字的位置。
则我们将第二关键字倒序枚举,即从大到小枚举第二关键字,对于第一关键字相等的后缀,第二关键字大的分配到的排名应该越大,即sa[w[rank[i]]–]=rank[i]。
给出样例:
对于基排我们有
10,11,12,13,先将十位个位分开丢进桶
1:0 1 2 3
则13分配到的排名应该尽量大,即sa[x]=(13的位置)和sa[y]=(12的位置),x>y。
后缀数组中的实现为,先枚举到第二关键字大的3,然后rank[]得到其第一关键字为1,w表示1这个桶中最大的那个数被取出时的排名,实现了13的排名取值。
给出模板:
inline void Sa()
{
int m=127,u,v;
for(int i=1;i<=m;++i) w[i]=0;
for(int i=1;i<=n;++i) w[x[i]=sr[i]]++;
for(int i=1;i<=m;++i) w[i]+=w[i-1];
for(int i=n;i>=1;--i) sa[w[x[i]]--]=i;
for(int j=1;j<=n;j*=2)
{
int cnt=0;
for(int i=n-j+1;i<=n;++i) rank[++cnt]=i;
for(int i=1;i<=n;++i)
if(sa[i]>j) rank[++cnt]=sa[i]-j;
for(int i=1;i<=m;++i) w[i]=0;
for(int i=1;i<=n;++i) w[x[i]]++;
for(int i=1;i<=m;++i) w[i]+=w[i-1];
for(int i=n;i>=1;--i) sa[w[x[rank[i]]]--]=rank[i];
m=0;
for(int i=1;i<=n;++i)
{
u=sa[i];v=sa[i-1];
if(x[u]!=x[v]||x[u+j]!=x[v+j]) ++m;
rank[u]=m;
}
if(m==n) break;
for(int i=1;i<=n;++i) x[i]=rank[i];
}
int j=0;
for(int i=1;i<=n;++i)
{
v=sa[rank[i]-1];
j=max(0,j-1);
while(sr[i+j]==sr[v+j]) ++j;
hei[rank[i]]=j;
}
}
若干技巧(持续更新,有待发现
很多题不好做就先二分再想想怎么check
本质不同的子串个数为:
∑in−sa[i]−hei[i]
,显然对于sa[i]这个位置,有hei[i]个相同的前缀,则之后贡献的是不同的