Further Reading
https://oi-wiki.org/string/sa/
http://riteme.site/blog/2016-6-19/sais.html
正题
后缀数组大致是怎么做的就不说了,网上很多教程。
练习题大多在后缀数组上的思想是依据height与子串的刻画,在这里讲讲套路。
后缀数组:
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int n,sum[233],rk[N],sa[N],t[N],tmpsa[N];
char s[N];
bool same(int x,int y,int l){
if(t[x]!=t[y]) return false;
if((x+l>n)^(y+l>n)) return false;
if(x+l>n) return true;
return t[x+l]==t[y+l];
}
void getsa(int m=233,int tot=0){
for(int i=1;i<=n;i++) rk[i]=s[i],sum[rk[i]]++;
for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
for(int i=1;i<=n;i++) sa[sum[rk[i]]--]=i;
for(int j=1;j<=n;j<<=1){
tot=0;for(int i=n-j+1;i<=n;i++) tmpsa[++tot]=i;
for(int i=1;i<=m;i++) sum[i]=0;
for(int i=1;i<=n;i++) sum[rk[tmpsa[i]]]++,t[i]=rk[i];
for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
for(int i=n;i>=1;i--) sa[sum[rk[tmpsa[i]]]--]=tmpsa[i];
for(int i=1;i<=n;i++) rk[sa[i]]=(i>1&&same(sa[i],sa[i-1],j))?m:++m;
}
}
int main(){
scanf("%s",s+1);n=strlen(s+1);
getsa();
for(int i=1;i<=n;i++) printf("%d ",sa[i]);
}
后缀数组的height数组表示的是rk为i后缀和rk为i-1的后缀的lcp长度。具体求法可以令初值为height[rk[i]]=max(height[rk[i]-1]-1,0),然后暴力扩展,正确性考虑将上一个匹配的lcp去掉第一位,复杂度考虑只会下降n次,那么总共只会暴力扩展2n次。
将所有height[i]>=k的后缀粘在一次
一般是把height倒着来做,然后维护一个带权并查集,并查集上维护所需信息。
例题:有一个字符串,你需要找到一个最长的子串,在字符串中出现了两次,且出现的这两次不重叠。
只需要按照套路,在并查集上维护开头的min和max即可。
将所有串接在一起,求任意两个后缀的lcp
我们只需要将所有串接在一起,中间用'$'符号链接,根据中值定理,可以求任意两个串的最长公共子串。
例题:给定两个字符串,求出最长公共子串的长度。
这题只需要连在一起,求相邻两个不同所属的height最大值即可,证明考虑中值定理。
上面两个结合起来就可以这么考:
相当于求有多少个lcp(a,c)>=l,那么就将height>=l的粘在一起,用并查集维护区间内属于1的个数和属于2的个数。
子串的刻画
如何将所有的子串输出?
后缀排序后,我们只需要从height[i]+1开始输出当前串,这样一定不会重复。
例题:给定一个字符串,每次给定一个子串,求出有多少个本质不同的子串字典序小于它。
首先找到该子串在后缀数组中的第一次出现的位置,然后将前面的长度-height加起来就好了,自己的单独算。
下面就是一堆例题:
T1
考虑怎样的一个串是unique子串,对于串进行后缀排序后,求出height,那么以sa[i]开头的unique串的最小右端点为p[i]=max(height[i],height[i+1])。
那么相当于我们要在[l,r-k+1]中p[i]<=r,然后在其中找最小的rank即可。离线用线段树维护或者直接上树套树即可。
T2
同样是按照height从大往小一次合并,然后维护区间内数的个数,记录一下上一次合并的时间戳,在两个并查集合并的时候,用(当前时间-时间戳)*区间后缀个数来更新ans[区间后缀个数] 即可。
总结
总结一下,可以知道一些有关lcp的复杂问题可以直接使用后缀数组轻松解决,实际上后缀数组就是后缀树关于叶子的dfs序,所以大部分问题都可以直接放到后缀树上考虑,像上面粘区间的一系列操作,对应到后缀树上实际上就是从下往上合并。
与后缀自动机比较,还是要多做题才能熟练掌握。