作者:Andy__lee
链接:https://blog.nowcoder.net/n/6b4a93e186ed4a358321de6a7c3b4f19
来源:牛客网
定义
维基百科 - 后缀数组
让我们来看一下 wiki 上的定义:
在计算机科学里, 后缀数组(英语:suffix array)是一个通过对字符串的所有后缀经过排序后得到的数组。此数据结构被运用于全文索引、数据压缩算法、以及生物信息学。
令字符串
![equation?tex=+S%3DS%5B1%5DS%5B2%5D...S%5Bn%5D%EF%BC%8C+S%5Bi%2Cj%5D+](https://i-blog.csdnimg.cn/blog_migrate/e326e2a484e6668f7241afc5d457b18d.png)
![equation?tex=S](https://i-blog.csdnimg.cn/blog_migrate/e6217fb4a8fa397deeec408fbaf501a3.png)
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=j](https://i-blog.csdnimg.cn/blog_migrate/ebe2c52df33e9163198c974359a197f2.png)
![equation?tex=S](https://i-blog.csdnimg.cn/blog_migrate/e6217fb4a8fa397deeec408fbaf501a3.png)
![equation?tex=A](https://i-blog.csdnimg.cn/blog_migrate/a86f8657535c007558ea628b4b5b9c7a.png)
![equation?tex=S](https://i-blog.csdnimg.cn/blog_migrate/e6217fb4a8fa397deeec408fbaf501a3.png)
满足
![equation?tex=%5Cforall+1%3Ci%5Cleq+n+%2C%E6%9C%89+S%5BA%5Bi-1%5D%2Cn%5D%3CS%5BA%5Bi%5D%2Cn%5D+](https://i-blog.csdnimg.cn/blog_migrate/6774e12d8f2e65b5e2bc32c2e24a8d3a.png)
求法
大体思想
这玩意可以使用倍增来求,当然也有常数比较大的
![equation?tex=+O%28n%29+](https://i-blog.csdnimg.cn/blog_migrate/2fdcd731f74b64a86cc72f88f0177d83.png)
中心思想就是假设我们求出了考虑每个后缀前
![equation?tex=w](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png)
![equation?tex=2w+](https://i-blog.csdnimg.cn/blog_migrate/f0869b832e1a3d7c5bf89a003644224b.png)
![equation?tex=w%3D1+](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png%3D1+)
![equation?tex=w+%5Ctimes+2+](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png+%5Ctimes+2+)
![equation?tex=rk+](https://i-blog.csdnimg.cn/blog_migrate/c411cf067a0be2bd624b7cfad8fa20c4.png)
![equation?tex=+tp+](https://i-blog.csdnimg.cn/blog_migrate/36db8187acc39f9b0d5c80a04a4dd649.png)
![equation?tex=rk+](https://i-blog.csdnimg.cn/blog_migrate/c411cf067a0be2bd624b7cfad8fa20c4.png)
![equation?tex=tp](https://i-blog.csdnimg.cn/blog_migrate/65686776d4b518cb5c27715c120856c9.png)
![equation?tex=w](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png)
![equation?tex=tp](https://i-blog.csdnimg.cn/blog_migrate/65686776d4b518cb5c27715c120856c9.png)
![equation?tex=+rk+O%28n%29+](https://i-blog.csdnimg.cn/blog_migrate/893f2d7c83080fe6ed1adffd4a1b6464.png)
![equation?tex=rk](https://i-blog.csdnimg.cn/blog_migrate/693e94c8fa3d51fb63e6daba032e223c.png)
![equation?tex=tp](https://i-blog.csdnimg.cn/blog_migrate/65686776d4b518cb5c27715c120856c9.png)
![equation?tex=rk](https://i-blog.csdnimg.cn/blog_migrate/693e94c8fa3d51fb63e6daba032e223c.png)
相信大家看完后肯定是一脸 mb,接下来我来详细的说一下算法过程。
算法过程
首先我们来定义一些变量:
![equation?tex=sa_i+](https://i-blog.csdnimg.cn/blog_migrate/2a5ffa6e186e0dfc12eb38d23ca399e2.png)
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=s](https://i-blog.csdnimg.cn/blog_migrate/4aa2212c7a3ae4ce1b94a8e3bcb39211.png)
![equation?tex=n](https://i-blog.csdnimg.cn/blog_migrate/ce59f55893efd960f1dbdfcd5c192a96.png)
首先按照定义,显然有
![equation?tex=%5Cforall+i+%5Cin+%5B1%2Cn%5D+%2Csa_%7Brk_i%7D+%3D+rk_%7Bsa_i%7D+%3D+i+](https://i-blog.csdnimg.cn/blog_migrate/5fc660cbacc6f93867733c3d57498096.png)
也就是说明了这两个数组可以互相
![equation?tex=+O%28n%29+](https://i-blog.csdnimg.cn/blog_migrate/2fdcd731f74b64a86cc72f88f0177d83.png)
开始时
![equation?tex=w%3D1+](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png%3D1+)
![equation?tex=+%28s_i%2Ci%29+](https://i-blog.csdnimg.cn/blog_migrate/2d774a84597fa1ab573dc1d554aa8925.png)
![equation?tex=w](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png)
![equation?tex=2w+](https://i-blog.csdnimg.cn/blog_migrate/f0869b832e1a3d7c5bf89a003644224b.png)
现在我们考虑如何求出
![equation?tex=tp+](https://i-blog.csdnimg.cn/blog_migrate/65686776d4b518cb5c27715c120856c9.png+)
p = 0;
FOR(i,1,w) tp[++p] = N-w+i;
FOR(i,1,N) if(sa[i] > w) tp[++p] = sa[i]-w;
首先对于长度
![equation?tex=%3Cw+](https://i-blog.csdnimg.cn/blog_migrate/4669748c0621ad0359e0185350f36079.png)
然后对于长度
![equation?tex=%3E+w+](https://i-blog.csdnimg.cn/blog_migrate/ebd119fc5ebf568637abee6ee464019d.png)
![equation?tex=i-w+](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png-w+)
![equation?tex=w](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png)
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=w](https://i-blog.csdnimg.cn/blog_migrate/1c7a330b82678d6b033d9748f5120af9.png)
![equation?tex=2w](https://i-blog.csdnimg.cn/blog_migrate/f35cd218270e2a423bd3f0d147c508c1.png)
所以代码就是按照上一轮的顺序从小到大枚举了每个长度
![equation?tex=%3E+w+](https://i-blog.csdnimg.cn/blog_migrate/ebd119fc5ebf568637abee6ee464019d.png)
之后我们搞个比较高效的排序(基数排序)排一下。
然后因为在这个倍增过程中可能有的后缀的
![equation?tex=rk+](https://i-blog.csdnimg.cn/blog_migrate/c411cf067a0be2bd624b7cfad8fa20c4.png)
![equation?tex=rk+](https://i-blog.csdnimg.cn/blog_migrate/c411cf067a0be2bd624b7cfad8fa20c4.png)
std::swap(tp,rk); // rk 没用了,当然这里最好写指针交换
rk[sa[1]] = p = 1;
FOR(i,2,N) rk[sa[i]] = (tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w]) ? p : ++p;
去重的原理和上面的类似:如果前半段和后半段在上一轮的rkrk相同的话那它们当前就是相同的。
然后就做完了。。。。
现在我们要解决的是找到一种高效的排序方法,首先显然你不能用
![equation?tex=O%28nlogn%29+](https://i-blog.csdnimg.cn/blog_migrate/c7b6dfbae0737a86232acdb455dfc2c2.png)
所以我们考虑
![equation?tex=O%28n%29+](https://i-blog.csdnimg.cn/blog_migrate/03a07026c2c96244a4dac61db3494f89.png)
先放一下代码:
inline void sort(){
FOR(i,0,M) tax[i] = 0;
FOR(i,1,N) tax[rk[i]]++;
FOR(i,1,M) tax[i] += tax[i-1];
ROF(i,N,1) sa[tax[rk[tp[i]]]--] = tp[i];
}
首先我们把桶清空,然后统计每种
![equation?tex=rk+](https://i-blog.csdnimg.cn/blog_migrate/c411cf067a0be2bd624b7cfad8fa20c4.png)
然后我们重点关注最后一句是在干什么。首先我们按第二关键字从大到小枚举(
![equation?tex=rk+](https://i-blog.csdnimg.cn/blog_migrate/c411cf067a0be2bd624b7cfad8fa20c4.png)
![equation?tex=tax_%7Brk_%7Btp_i%7D%7D+](https://i-blog.csdnimg.cn/blog_migrate/ba4fba6345e38aba633d2ab9bcbd4f5c.png)
是不是很好理解?这样后缀数组就写完了。
附上Luogu - 后缀排序的代码:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define Re register
#define LL long long
#define U unsigned
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c)
#define SROF(i,a,b,c) for(Re int i = a;i >= b;i-=c)
#define CLR(i,a) memset(i,a,sizeof(i))
#define BR printf("--------------------n")
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1000000+5;
char str[MAXN];
int N,sa[MAXN],tax[MAXN],M;
int pool1[MAXN],*rk = pool1,pool2[MAXN],*tp = pool2;
inline void sort(){
FOR(i,0,M) tax[i] = 0;
FOR(i,1,N) tax[rk[i]]++;
FOR(i,1,M) tax[i] += tax[i-1];
ROF(i,N,1) sa[tax[rk[tp[i]]]--] = tp[i];
}
inline void SuffixSort(){
M = 75;
FOR(i,1,N) rk[i] = str[i]-'0'+1,tp[i] = i;
sort();
for(int w = 1,p = 0;p < N;w <<= 1,M = p){
p = 0;
FOR(i,1,w) tp[++p] = N-w+i;
FOR(i,1,N) if(sa[i] > w) tp[++p] = sa[i]-w;
sort();
std::swap(tp,rk);
rk[sa[1]] = p = 1;
FOR(i,2,N) rk[sa[i]] = (tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w]) ? p : ++p;
}
FOR(i,1,N) printf("%d ",sa[i]);
}
int main(){
scanf("%s",str+1);N = strlen(str+1);
SuffixSort();
return 0;
}
Height 数组
后缀数组如果只能排序的话那貌似没什么用,大多数后缀数组题目主要还是考察 Height 数组的性质。
首先扔一些定义。。。
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=lcp%28a%2Cb%29+](https://i-blog.csdnimg.cn/blog_migrate/f2c23791d1e7a79456ab607de8ac637d.png)
![equation?tex=a](https://i-blog.csdnimg.cn/blog_migrate/3a1b450ba33b8215b0d69885896f7309.png)
![equation?tex=b](https://i-blog.csdnimg.cn/blog_migrate/111cf5c2cf1cc54e0b93080ff933d2e8.png)
定义
![equation?tex=height_i+%3D+lcp%28sa_i%2Csa_%7Bi-1%7D%29+](https://i-blog.csdnimg.cn/blog_migrate/b62b607f738b4b8711b5ace2bbcea543.png)
如果直接枚举后缀那还求后缀数组干嘛,所以说我们要考虑能否通过后缀数组求出的信息来线性推出
![equation?tex=height+](https://i-blog.csdnimg.cn/blog_migrate/350090455a1f58231b44523af31f0c95.png)
对于
![equation?tex=height+](https://i-blog.csdnimg.cn/blog_migrate/350090455a1f58231b44523af31f0c95.png)
![equation?tex=height_%7Brk_%7Bi%7D%7D+%5Cgeq+height_%7Brk_%7Bi-1%7D%7D-1+](https://i-blog.csdnimg.cn/blog_migrate/ee28f5fec3d52f61c9fc48fd63d061a1.png)
代码:
inline void get(){
int j,k = 0;
FOR(i,1,N){
if(k) --k;
int j = sa[rk[i]-1];
while(str[i+k] == str[j+k]) ++k;
height[rk[i]] = k;
}
}
经典应用
求任意后缀的最大 lcp
![equation?tex=lcp%28x%2Cy%29+%3D+%5Cmin_%7Bi%3Dx%7D%5Ey+height_i](https://i-blog.csdnimg.cn/blog_migrate/93b0603855f8924c088c1f83fc79f19e.png)
这东西随便维护一个区间极值就可以了,
![equation?tex=O%281%29+](https://i-blog.csdnimg.cn/blog_migrate/bb5ca52f9637497e187d19ba536f620e.png)
可重叠最长重复子串
意思是求最长的子串使得在字符串中重复出现过。
根据定义就是 height 数组中的最大值
不可重叠最长重复子串
POJ - 1743
我们来考虑二分答案。假设现在我们二分的答案是
![equation?tex=k](https://i-blog.csdnimg.cn/blog_migrate/9d4a1163d2eb1bb72228d1279049c2d8.png)
![equation?tex=%3Ek+](https://i-blog.csdnimg.cn/blog_migrate/4a7c7adda5995f9e3056ff52a0c88298.png)
然后考虑在一些地方将 height 分开,保证每一段最小
![equation?tex=height+%5Cgeq+x+](https://i-blog.csdnimg.cn/blog_migrate/350090455a1f58231b44523af31f0c95.png%5Cgeq+x+)
本质不同的子串数量
注意到子串 = 后缀的前缀。
对于排名为
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=n+-+sa_i+%2B+1+](https://i-blog.csdnimg.cn/blog_migrate/ce59f55893efd960f1dbdfcd5c192a96.png+-+sa_i+%2B+1+)
![equation?tex=height_i+](https://i-blog.csdnimg.cn/blog_migrate/b29c31db905527f7de0f5e0a54a2ec53.png)
![equation?tex=i-1+](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png-1+)
所以答案就是
![equation?tex=%5Csum_%7Bi%3D1%7D%5En+%28n-sa_i+%2B+1-height_i%29+](https://i-blog.csdnimg.cn/blog_migrate/b3af9c669595cee2d8486695023762de.png)
查看作者更多博客:https://blog.nowcoder.net/rainair