UNPKG

oi-wiki

Version:
151 lines (103 loc) 4.43 kB
## 1. 前言 ### 后缀数组和后缀树 > 在字符串处理当中,后缀树和后缀数组都是非常有力的工具。其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。——百度百科 ### 各种定义 `子串`: 就是子串 [捂脸] `后缀`: 就是从 $i$ 这个位置开始到该字符串的末尾的一个子串 `字符串大小`: $a$ 和 $b$ 这两个串,从头开始逐个字符按照 ASSIC 码进行比较 `后缀数组`$sa[i]$ 代表该字符串的 $len$ 个后缀中,排名为 $i$ 的后缀是第 $sa[i]$ 个后缀 `名词数组`$rank[i]$ 代表第 $i$ 个后缀排名为 $rank[i]$ ## 2. 一些构造方法 ### 最简单的暴力 把所有的后缀拆出来,然后 sort 思想较为简单,可自行尝试实现 ```cpp #include <bits/stdc++.h> using namespace std; int rank[123], sa[123]; struct Str { string s; int wei; friend bool operator < (Str a1,Str a2) { return a1.s<a2.s; } } k[123] ; int main() { string s; cin >> s; int len=s.size()-1; for (int i=0;i<=len;i++) { k[i].wei=i; for (int j=i;j<=len;j++) k[i].s=k[i].s+s[j]; } sort (k,k+len+1); for (int i=0;i<=len;i++) { rank[k[i].wei]=i; sa[i]=k[i].wei; } exit(0); } ``` ### 倍增 这个就是一般人写后缀数组用的方法 复杂度是 $O(nlogn)$ 前提是你要先会 ** 基数排序 ** 假设我们有这样一个字符串 `aabaaaab` 然后我们把所有的后缀列举出来: ![](images/sa1.png) 然后用基数排序的方式,按照每个后缀的第一个字母进行排序,呈现这样子的效果: ![](images/sa2.png) 接着我们以第二个字母为关键字,在首字母有序的基础上进行排序,这个时候,我们把首字母相同的后缀拿出来单看 对于每一组首字母相同的后缀,首字母是对排序没有影响的,所以可以直接按照第二个字母进行基数排序,同样,对于首字母不同的后缀,由于按照首字母排序时,他们的相对大小已经确定,当按照第二个字母排序时,不会出现 `原来 a>b,现在 b>a` 的现象,所以我们可以看成一直在做区域内的排序,这之后变成这样: ![](images/sa3.png) 第三字母同理........ 这样子我们可以处理这个问题,可是复杂度还是没有到达一个我们可以接受的范围 所以我们引入 ** 倍增 ** 当我们按照每个后缀的前 $2^k$ 个字母进行完排序后,那么我们把后缀的前 $2^{k+1}$ 看做前后两个 $2^k$, 这样我们就可以把这前后两个 $2^k$ 作为之前说的 `首字母``第二个字母` 了,然后进行上述过程,就可以在 $O(nlogn)$ 的复杂度内处理这个问题了 ```cpp #include <bits/stdc++.h> using namespace std; int n; int sa[150], x[150], c[150], y[150]; char a[150]; inline void SA () { int m=128; for (int i=0;i<=m;i++) c[i]=0; for (int i=1;i<=n;i++) c[x[i]]++; for (int i=1;i<=m;i++) c[i]+=c[i-1]; for (int i=n;i;i--) sa[c[x[i]]--]=i; for (int k=1,p;k<=n;k<<=1) { p=0; for (int i=n;i>n-k;i--) y[++p]=i; for (int i=1;i<=n;i++) if(sa[i]>k) y[++p]=sa[i]-k; for (int i=0;i<=m;i++) c[i]=0; for (int i=1;i<=n;i++) c[x[i]]++; for (int i=1;i<=m;i++) c[i]+=c[i-1]; for (int i=n;i;i--) sa[c[x[y[i]]]--]=y[i]; p=y[sa[1]]=1; for (int i=2,a,b;i<=n;i++) { a=sa[i]+k>n ? -1 : x[sa[i]+k]; b=sa[i-1]+k>n ? -1 : x[sa[i-1]+k]; y[sa[i]]= (x[sa[i]]==x[sa[i-1]]) && (a==b) ? p : ++p ; } swap (x,y); m=p; } } int main () { scanf("%s",a+1); n=strlen (a+1); for (int i=1;i<=n;i++) x[i]=a[i]; SA (); for (int i=1;i<=n;i++) printf("%d",sa[i]); exit(0); } ``` 代码里 $x[i]$ 就是 $rank[i]$ $y[i]$ :假设 $y[i]=a\ ,\ y[i+1]=b$ 那么在原串中 从 $a+2^k$ 开始的 $2^k$ 个字符组成的子串 ** 小于等于 ** 从 $b+2^k$ 开始的 $2^k$ 个字符组成的子串 最好理解这个代码时,每一步都结合这基数排序来考虑