【总结】 后缀数组及应用
前言
后缀数组还是很难理解的,所以直接背个板子就好了。——Anson语录
定义
为了下面方便,给出一些定义:
int sa[N];//从小到大排序后,第i个后缀是哪一个
int rank[N];//第i个后缀是sa中的哪一个
//由上可知:sa[rank[i]]=i;
int height[N];//LCP(Suffix(sa[i]),Suffix(sa[i-1])).
方法
由于DC-3太复杂了,所以这里只介绍倍增法:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
char s[1000010];
int n,sa[1000010],rank[1000010],height[1000010],t1[1000010],t2[1000010],c[1000010];
int a[1000010];
void SAsort(int n,int m){
int *x=t1,*y=t2;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]=a[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
for(int k=1,p=0;k<=n && p<=n;k<<=1){
p=0;
for(int i=n-k+1;i<=n;i++)y[++p]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[y[i]]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
std::swap(x,y);
p=2;x[sa[1]]=1;
for(int i=2;i<=n;i++)
x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
m=p;
}
for(int i=1;i<=n;i++)rank[sa[i]]=i;
int f=0;
for(int i=1;i<=n;i++){
int j=sa[rank[i-1]];
if(f)f--;
while(s[j+f]==s[i+f])f++;
height[rank[i]]=f;
}
}
int main(){
scanf("%s",s);n=strlen(s);
for(int i=1;i<=n;i++)a[i]=s[i-1];
SAsort(n,10000);
for(int i=1;i<=n;i++)
printf("%d%c",sa[i],i==n?'\n':' ');
return 0;
}
这样你就可以求出SA的一些必要的数组(希望大家可以直接背模板)
应用
Problem1
有一个字符串s,求它的子串中至少出现过两次的最长的子串。
Solution1
考虑height的定义:两个rank值相近的字符串的prefix,那么很显然这样子一定比rank值远一些的更优啊!
所以答案就是\(max(height[i])(i∈(1,n))\)
Problem2
有一个字符串s,求它的子串中至少出现过两次的最长的子串(不可重叠)。
Solution2
二分答案然后分成很多个集合就可以了。
Problem3
给定一个字符串s,求它不同的子串的个数。
Solution3
考虑一下每一个后缀可以提供\(len-len1\)个子串,然后考虑有\(height_i\)个重复了。
直接加起来就好了
后记
其实还没有写完(2018.12.20 16:39)
感谢菊队的上古PPT