后缀数组
处理多个字符串用AC自动机就很方便了,但是AC自动机需要空间巨大,很可能爆内存,所以后缀数组被引入了。
后缀数组主要是两个数组
第一个是sa数组,sa[i]代表排名为i的后缀的首字母的位置;
第二个是rnk数组,rnk[i]代表首字母位置为i的后缀的排名。
最先要做的就是求出这两个数组。
其实有很简单的办法,那就是把后缀sort一下,排个序,统计一下排名,万事大吉。
如果时间复杂度允许的话……
还是用另一种方法吧:倍增+基数排序
先把每个后缀按照首字母排序,按照首字母给每个后缀一个名次;
再把每个后缀的排名当做权值,把相邻的两个结合成一个二元组(后面没有元素就用0占位),再进行排序,这样又得到一个新的名次;
之后用倍增的思想,把当前位置与后面第二个的排名结合成一个二元组,再排序;
然后把当前位置与后面第四个的排名结合成一个二元组,再排序;
……
直到没有相同的排名(只要连接的长度大于等于字符串的长度,就可以保证排名一定互不相同了)。
时间复杂度O(nlogn)
注意事项:
1、在每次基数排序时要把sum(桶数组)数组清空;
2、按照关键字排序要先排第二位再排第一位;
3、如果后面没有元素要用0占位;
4、每次sa数组要按顺序赋值。
后缀数组模板代码如下:
(如果该代码与其他人的代码雷同,纯属……没错,我就是照PoPoQQQ大神抄的)
void get_rank()
{
for(int i=1;i<=tot;i++) sum[s[i]]++;
for(int i=1;i<=127;i++) sum[i]+=sum[i-1];
for(int i=tot;i>=1;i--) tmp[sum[s[i]]--]=i;
for(int i=1;i<=tot;i++)
{
if(i==1 || s[tmp[i]]!=s[tmp[i-1]]) t++;
rnk[tmp[i]]=t;
}
}
void radix_sort(int key[],int order[])
{
for(int i=0;i<=tot;i++) sum[i]=0;
for(int i=1;i<=tot;i++) sum[key[i]]++;
for(int i=1;i<=tot;i++) sum[i]+=sum[i-1];
for(int i=tot;i>=1;i--) tmp[sum[key[order[i]]]--]=order[i];
for(int i=1;i<=tot;i++) order[i]=tmp[i];
}
void suffix_array()
{
get_rank();
for(int j=1;j<=tot;j<<=1)
{
for(int i=1;i<=tot;i++)
{
X[i]=rnk[i];
Y[i]=i+j>tot?0:rnk[i+j];
sa[i]=i;
}
radix_sort(Y,sa);
radix_sort(X,sa);
t=0;
for(int i=1;i<=tot;i++)
{
if(i==1 || X[sa[i]]!=X[sa[i-1]] || Y[sa[i]]!=Y[sa[i-1]]) t++;
rnk[sa[i]]=t;
}
}
}
【JSOI2007 字符加密Cipher】bzoj1031
题目大意:
给一个字符串,把这个字符串变成环,然后以每个字母为首字母沿着环形成一个新的字符串,按照字典序排序,然后依次输出尾字母。
题目分析:
处理环的问题,我们常用的方法就是把原串复制一遍接在后面。
这道题就可以把原串接在后面然后用后缀数组排序,只取首字母在第一个串里的后缀,按sa数组的顺序输出尾字母。
主函数代码如下:
int main()
{
scanf("%s",s+1);
tot=strlen(s+1);
for(int i=1;i<=tot;i++) s[tot+i]=s[i];
tot*=2;
suffix_array();
tot/=2;
for(int i=1;i<=tot*2;i++)
{
if(sa[i]>tot) continue;
printf("%c",s[sa[i]+tot-1]);
}
printf("\n");
return 0;
}