后缀数组

模板

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;
}
posted @ 2021-02-02 20:30  zjjws  阅读(44)  评论(0)    收藏  举报