前言
字符串算法一直是我最不愿碰的东西,包括DP。
什么是后缀?
”ababs”
中所有的后缀串为”ababs”,”babs”,”abs”,”bs”,”s”
我们按照字典序排列即为
”ababs”,’abs”,”babs”,”bs”,”s”
而后缀数组就是用来求后缀的字典序的
定义
SA[i]为排名为i的后缀第一个字符在主串里的位置.
譬如上面的例子
SA[1]=1,SA[2]=3,SA[3]=2,SA[4]=4,SA[5]=5
Rank[i]记录首字符在i位置的后缀的排名
很明显SA与Rank互为逆运算
简单讲:
SA是排名第几的位置
Rank是此位置排名第几
实现
首先我们用基数排序求到每个字符串中的字符的名次。这里我们就以aabaaaab为例,如图:
第一轮之后的结果就是这样。那么第二轮,就是对每个后缀的前两个字符进行排序。因为每个单字符的名次已经得出。就相当于对一个二元组(x,y)进行排序,且以 x 为第一关键字,以 y 为第二关键字。这么排序之后就得到了这么一幅图:
接下来继续倍增,对前四个字符进行排序,此时依旧相当于对一个二元组(x,y)排序,排序规则相同。此时的 x 和 y 分别表示前两个字符的名次和第三、四个字符的名次(在第二次排序已经将其全部求出),所以同上进行排序,就能得到这样的一幅图:
此时我们发现每个排名都不相同,即可停止倍增了。
这里给出构造后缀数组的代码
int make_SA(int n)
{
int m=150;//确定边界
for(int i=1;i<=n;i++) t[x[i]=s[i]]++;
for(int i=1;i<=m;i++) t[i]+=t[i-1];
for(int i=n;i>=1;--i) SA[t[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int p=0;
for(int i=0;i<=m;i++)y[i]=0;//桶清空
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++) if(SA[i]>k) y[++p]=SA[i]-k;
for(int i=0;i<=m;i++) t[i]=0;
for(int i=1;i<=n;i++) t[x[y[i]]]++;
for(int i=1;i<=m;i++) t[i]+=t[i-1];
for(int i=n;i>=1;i--) SA[t[x[y[i]]]--]=y[i];
swap(x,y);
x[SA[1]]=p=1;
for(int i=2;i<=n;i++)
x[SA[i]]=check(SA[i],SA[i-1],k)?p:++p;
if(p>=n)break;
m=p;
}
}