定义:
suf[i]表示以i为开头的后缀
rank[i]表示suf[i]的排名,sa[i]表示排名为i的后缀
height[i]表示sa[i]和sa[i-1]的lcp
h[i]表示suf[i]和suf[sa[rank[i]-1]]的lcp
sa[rank[i]]=i,所以只要能求出rank,就可以求sa
倍增
求rank相当于把n个后缀排序,直接暴力排,显然是O(n^2*log(n))
我们可以考虑假如已经处理了每个位置开始的长度为k的字符串的排名,对于下一次排序,对于一个位置i我们可以以i的排名为第一关键字,以i+k的排名为第二关键字(i+k>n记做0),那么我们就可以处理出每个位置开始的长度为2k的字符串的排名。
也就是log(n)次即可,如果每次用快排那就是O(nlog^2n)
每次的排名肯定是<=n的,那我们用基数排序就可以O(n)排序。
对于两个关键字的基数排序,我们可以先处理出每个数的第二关键字的排名,然后对第一关键字排序时按第二关键字的排名插入即可。
如何处理height呢?
我们可以直接求h,因为height[rank[i]]=h[i]
有一个很好的性质h[i]>=h[i+1]-1,利用这个就可以做到O(n)处理height
简单应用
两个后缀suf[i]和suf[j]的lcp={height[rank[i]+1]~height[rank[j]]}的最小值
void getsa(){
int i,k=1,num,j,x,y;
fo(i,1,n) d[s[i]]++;
fo(i,1,255) d[i]+=d[i-1];
fod(i,n,1) sa[d[s[i]]--]=i;
num=0;
fo(i,1,n){
if (s[sa[i]]!=s[sa[i-1]]) num++;
rank[sa[i]]=num;
}
for(;num<n;){
num=0;
fo(i,0,n) d[i]=0;
fo(i,1,n) d[b[i]=rank[i+k]]++;
fo(i,1,n) d[i]+=d[i-1];
fod(i,n,1) c[d[b[i]]--]=i;
fo(i,0,n) d[i]=0;
fo(i,1,n) d[rank[i]]++;
fo(i,1,n) d[i]+=d[i-1];
fod(i,n,1) sa[d[rank[c[i]]]--]=c[i];
fo(i,1,n){
x=sa[i],y=sa[i-1];
if ((rank[x]!=rank[y])||(rank[x]==rank[y]&&b[x]!=b[y])) num++;
c[sa[i]]=num;
}
fo(i,1,n) rank[i]=c[i];
k<<=1;
}
k=0;
fo(i,1,n){
if (k) k--;
j=sa[rank[i]-1];
for(;i+k<=n&&j+k<=n&&s[i+k]==s[j+k];k++);
height[rank[i]]=k;
}
fo(i,1,n) w[i][0]=height[i];
two[0]=1;
fo(i,1,19)two[i]=two[i-1]<<1;
j=0;
fo(i,1,n){
if (i==two[j+1]) j++;
lo[i]=j;
}
fo(j,1,18)
fo(i,1,n-two[j]+1) w[i][j]=min(w[i][j-1],w[i+two[j-1]][j-1]);
}
int lcp(int x,int y){
if (x==y) return n-x+1;
x=rank[x],y=rank[y];
if (x>y) swap(x,y);x++;
int k=lo[y-x+1];
return min(w[x][k],w[y-two[k]+1][k]);
}