后缀数组
模板
Part 1 前置知识
基数排序和分治,这两个东西的思想很重要。
Part 2 流程
考虑基数排序的时候我们在干什么,逐关键字比较。
但是在字符串这里,逐关键字比较会显得非常愚蠢,为什么?因为字符串的长度不比数字的位数,朴素的这么排序肯定超时。
于是我们可以像之前一样,利用之前已经算出的信息,来达到优化的目的。
比如说我们已经知道了所有开始位置长度为 \(2\) 的时候的排名,那么长度为 \(4\) 的排名就可以利用之前算出来的信息加双关键字排序来获得。
一共 \(\log n\) 次,单次 \(\operatorname O(n)\)。
Part 3 代码
好歹是自己写出来了,虽然很丑。
#include <vector>
#include <stdio.h>
#include <string.h>
using namespace std;
inline char gc(){char c=getchar();for(;(c>'z'||c<'a')&&c!=EOF&&(c>'9'||c<'0')&&(c>'Z'||c<'A');c=getchar());return c;}
const int N=1e6+3;
int d[N];
int fir[N];
int sec[N];
int cutt[N];
int cuts[N];
int adt[N];
int ads[N];
struct gyq
{
int lens;
char x[N];
int sa[N];
int rk[N];
inline void init(){lens=0;return;}
inline void Read(){for(;(x[++lens]=gc())!=EOF;);lens--;return;}
inline void work()
{
memset(cutt,0,sizeof(cutt));
for(int i=1;i<=lens;i++)cutt[x[i]]++;
for(int i=1;i<=128;i++)adt[i]=adt[i-1]+cutt[i];
for(int i=1;i<=lens;i++)sa[adt[x[i]]--]=i,cutt[x[i]]--;
for(int i=1,j,k=0;i<=lens;i=j){k++;for(j=i;j<=lens&&x[sa[i]]==x[sa[j]];j++)fir[sa[j]]=k;}
for(int L=2;(L>>2)<=lens;L<<=1)
{
for(int i=1;i<=lens;i++)sec[i]=(i+(L>>1)<=lens)?(fir[i+(L>>1)]+1):(1),cutt[sec[i]]++,cuts[fir[i]]++;
for(int i=0;i<=lens;i++)adt[i+1]=adt[i]+cutt[i+1],ads[i+1]=ads[i]+cuts[i+1];
for(int i=1;i<=lens;i++)rk[adt[sec[i]]--]=i,cutt[sec[i]]--;
for(int i=lens;i>=1;i--)sa[ads[fir[rk[i]]]--]=rk[i],cuts[fir[i]]--;
for(int i=1,j,k=0;i<=lens;i=j){k++;for(j=i+1;j<=lens&&fir[sa[i]]==fir[sa[j]]&&sec[sa[i]]==sec[sa[j]];j++)fir[sa[j]]=k;fir[sa[i]]=k;}
}
for(int i=1;i<=lens;i++)rk[sa[i]]=i;
return;
}
inline void pri(){for(int i=1;i<=lens;i++)printf("%d ",sa[i]);printf("\n");return;}
}a;
int main()
{
a.init();a.Read();a.work();a.pri();
return 0;
}
$$\texttt{Dirty Deeds Done Dirt Cheap}$$