今天学了一下倍增求后缀数组,感觉还不错。
毕竟二分hash求后缀数组适用范围实在太小了
代码:
char s[Maxn];int n;
int t[Maxn],sa[Maxn],rank[Maxn],wr[Maxn],tmp[Maxn],y[Maxn];
/*y[i]排名为i的第二关键字的第一关键字的后缀开始位置*/
void get_sa(int m)
{
/*先用基数排序求出第一次的rank和sa*/
for(int i=1;i<=n;i++)t[rank[i]=s[i]]++;
for(int i=1;i<=m;i++)t[i]+=t[i-1];
for(int i=n;i;i--)sa[t[s[i]]--]=i;
/*len为当前倍增的长度,mx为当前最大的排名,若mx>=n则意味着完成排序*/
int len=1,mx=0;
while(mx<n)
{
int k=0;
/*这个范围内的后缀的第二关键字是最小的*/
for(int i=n-len+1;i<=n;i++)y[++k]=i;
for(int i=1;i<=n;i++)if(sa[i]>len)y[++k]=sa[i]-len;
/*得出y[i]后就可以直接开始基数排序了,wr[i]可以理解为第一次排序的s[i],为排序关键字*/
for(int i=0;i<=m;i++)t[i]=0;
for(int i=1;i<=n;i++)t[wr[i]=rank[y[i]]]++;
for(int i=1;i<=m;i++)t[i]+=t[i-1];
for(int i=n;i;i--)sa[t[wr[i]]--]=y[i];
/*tmp存临时排名*/
for(int i=1;i<=n;i++)tmp[i]=rank[i];
int rk=1;rank[sa[1]]=1;
for(int i=2;i<=n;i++)
{
if(tmp[sa[i]]!=tmp[sa[i-1]]||tmp[sa[i]+len]!=tmp[sa[i-1]+len])rk++;
rank[sa[i]]=rk;
}
len<<=1;mx=rk;m=rk;
}
}
/*设h[i]=height[rank[i]],有性质:h[i]>=h[i-1]-1,利用这个求height数组*/
int height[Maxn];
void get_height()
{
for(int i=1;i<=n;i++)
{
int j=sa[rank[i]-1],k=height[rank[i-1]];
if(k)k--;
while(s[i+k]==s[j+k])k++;
height[rank[i]]=k;
}
}