后缀数组

前言

本高一蒟蒻实在不会后缀数组,又溜回来学啦~(话说这是第几次了?)

感觉理解能力日益下降,一个小时才看懂板子(大雾)

后缀数组是个神奇的玩意,具体怎么用我也不知道。

本渣不会DC3,所以只讲倍增。

基本定义

子串:在字符串s中任取$i<=j$,从第i个字符到第j个字符形成的字符串即为s的子串,记上面这玩意为$s[i,j]$。

后缀:从字符串某个位置i到字符串末尾形成的子串,即$s[i,len](1<=i<=len)$,记以i为开头的后缀为$suf(i)$。

后缀数组(sa):表示排名为i的后缀的起始位置。

排名数组(rk):表示起始位置为i的后缀的排名。

开始搞事qwq~

理解了上面这些定义之后,我们就可以乱搞了。直接快排$O(n^2logn)$多舒服。

先丢个图~(然后本蒟蒻就自己写了个$O(nlog^2n)$的垃圾算法)

实际上上面这张图完成第三次排序时就可以停止了。

一开始,我们可以根据每一位字符的ascii码得到当前情况下该位字符的排名$x_i$。(排名当然可以重复啦~)

然后第k次排序,对于第i位,我们将$x_i$作为第一关键字,$x_{i+2^{k-1}}$作为第二关键字。(显然的,当$i+2^{k-1}>n$时,第i位的第二关键字为0)

我们来考虑一下这为什么是可行的:

初始情况下,每一位所表示的字符串为以当前位为开头长度为1的字符串。

第k(k>1)次排序中,每一位所表示的字符串为以当前位为开头长度为$2^k$的字符串。比较两个关键字,相当于先比较前$2^{k-1}$个字符,再比较后$2^{k-1}$个字符。然后更新x数组。

因此,经过$logn$次的排序后,每一位所表示的字符串即以当前位为开头的后缀。x数组即为该字符串对应的rk数组。

对于每次排序,使用快速排序可以获得$O(nlog^2n)$的优秀算法(大雾)

然而有些聪(sang)明(xin)睿(bing)智(kuang)的出题人是会卡的,我们来想一下如何得到更优的时间复杂度。

我们观察发现(显然的),只有两个关键字,且关键字大小是$O(n)$的,因此我们对关键字进行基数排序即可,时间复杂度$O(nlogn)$。(基础知识,自行百度)

代码

我们定义$x_i$为第i位的关键字,$y_i$为第二关键字排名第i的位置,m为当前关键字种类数(初始值设为128即可),c为桶。

一开始先对每一位求出x值(第1次排序),对c做前缀和可以知道第一关键字为i时,排名最大可以是多少:

1 for(int i=1;i<=n;i++)
2     c[x[i]=s[i]]++;
3 for(int i=2;i<=m;i++)
4     c[i]+=c[i-1];
5 for(int i=n;i;i--)
6     sa[c[x[i]]--]=i;

第k次排序时,第$n-2^{k-1}+1$位到第n位的第二关键字为0,显然第二关键字排名最前:

1 for(int i=n-k+1;i<=n;i++)
2     y[++num]=i;

若$sa[i]>2^{k-1}$,表明第$sa[i]$位可作为第$sa[i]-2^{k-1}$位的第二关键字:

1 for(int i=1;i<=n;i++)
2     if(sa[i]>k)
3         y[++num]=sa[i]-k;

清空桶,将$x_i$加入桶中,求前缀和:

1 for(int i=1;i<=m;i++)
2     c[i]=0;
3 for(int i=1;i<=n;i++)
4     c[x[i]]++;
5 for(int i=1;i<=m;i++)
6     c[i]+=c[i-1];

第二关键字越大,在第一关键字相同的桶中排名越大:

1 for(int i=n;i;i--)
2     sa[c[x[y[i]]]--]=y[i];

以上便完成一次基数排序。该次排序的sa数组已完成,可以直接循环求出新的x数组。

这里用y数组保存x数组,因为要用来计算新的x数组:

1 swap(x,y);x[sa[num=1]]=1;
2 for(int i=2;i<=n;i++)
3     x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;

后缀数组end.(后缀数组代码20行以内是真的哦~只要你缩行

完整代码如下:

 1 int n,c[100010]={0},x[200010],y[200010],sa[200010],rk[200010];
 2 char s[100010];
 3 inline void suffix_array(int m){
 4     for(int i=1;i<=n;i++)
 5         c[x[i]=s[i]]++;
 6     for(int i=2;i<=m;i++)
 7         c[i]+=c[i-1];
 8     for(int i=n;i;i--)
 9         sa[c[x[i]]--]=i;
10     for(int k=1;k<=n;k<<=1){
11         int num=0;
12         for(int i=n-k+1;i<=n;i++)
13             y[++num]=i;
14         for(int i=1;i<=n;i++)
15             if(sa[i]>k)
16                 y[++num]=sa[i]-k;
17         for(int i=1;i<=m;i++)
18             c[i]=0;
19         for(int i=1;i<=n;i++)
20             c[x[i]]++;
21         for(int i=1;i<=m;i++)
22             c[i]+=c[i-1];
23         for(int i=n;i;i--)
24             sa[c[x[y[i]]]--]=y[i];
25         swap(x,y);x[sa[num=1]]=1;
26         for(int i=2;i<=n;i++)
27             x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
28         if((m=num)==n)
29             break;
30     }
31     for(int i=1;i<=n;i++)
32         rk[sa[i]]=i;
33     return;
34 }

height数组

待更…(可能会咕~)

posted @ 2019-04-17 15:53  乖巧的小团子QwQ  阅读(153)  评论(0编辑  收藏  举报