后缀数组是处理字符串问题的一个非常常用而实用的工具,
比之后缀树,它的编程难度较低,复杂度较优,可以说几乎能替代后缀树
那他到底是个什么东西呢
其实就是一个字符串的所有后缀按字典序大小排序后的得到的字符串组
这个字符串组最有用的数两个东西:
Sai:
代表字典序排在第
i
位的后缀的左端点编号
那么这么构造这两个函数呢?
暴力排序?
显然不行,复杂度太高
我们可以考虑使用倍增思想和基数排序优化
先计算出每个后缀长度为
d
的前缀的
而后通过基数排序排除每个后缀长度为
2d
的前缀的
Sai
数组
(对于左端点为i的后缀,
[i+len,i+2len−1]
为第二关键字,
[i,i+len−1]
为第一关键字)
因为每个后缀长度不同,所以永远不可能相等
所以当当前的前缀组成的字符串组每个字符串均不相同时,即可结束循环
代码如下:
void get_sa(int n,int m){//n代表原字符串长度,m代表当前不重复前缀的个数
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)cnt[cmp1[i]=s[i]]++;
for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)sa[cnt[cmp1[i]]--]=i;
for(int d=1;d<=n;d*=2){//用每个后缀长度为d的前缀的大小关系推导长度为2d的大小关系
int tp=0;
//按第二关键字排序
for(int i=n-d+1;i<=n;i++)cmp2[++tp]=i;//以这些位置为起点的后缀的当前前缀的第二关键字为空
for(int i=1;i<=n;i++)if(sa[i]>d)cmp2[++tp]=sa[i]-d;//按长度为d的前缀的字典序for,若第一关键字完全则加入
//按第一关键字排序
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)cnt[cmp1[cmp2[i]]]++;
//cmp2[i]即按第二关键字排序好的前缀所属的后缀编号,cmp1[cmp2[i]]即该前缀第一关键字的字典序大小顺序
for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)sa[cnt[cmp1[cmp2[i]]]--]=cmp2[i];//基数排序
for(int i=1;i<=n;i++)cmp2[i]=cmp1[i];//cmp1数组在下述过程中会变化,因此用cmp2数组来替代tmp数组的作用
int pre=0;
for(int i=1;i<=n;i++){
if(cmp2[sa[i]]!=cmp2[sa[i-1]]||cmp2[sa[i]+d]!=cmp2[sa[i-1]+d])pre++;
//已按字典序排好,若排名为i的字符串与i-1不相等,那么他的字典序排名为i-1的排名加1,否则相等
cmp1[sa[i]]=pre;
}
if(pre>=n)break;//已经比较出每个后缀的大小关系,再进行倍增没有意义
m=pre;
}
for(int i=1;i<=n;i++)Rank[sa[i]]=i;
}
这样,我们就完成了求取后缀数组的操作
那某,这东东有什么用呢,反正很有用就对了,去多刷点题吧