[模板] 后缀数组
概述
后缀数组(Suffix Array), 是一种将字符串所有后缀排序的一种算法。
通过排序后的后缀,我们可以得到字符串的许多性质,如重复出现的字串等。
算法
\(n\) 表示字符串长度, \(\Sigma\) 表示字符集大小.
常见的后缀数组算法有:
- 倍增算法
- 时间复杂度 $ O(n \log n) $
- 常数较小
- 代码较短
- DC3算法
- 时间复杂度 $ O(n) $
- 常数较大
- 代码较长
- SA-IS算法
- 时间复杂度 $ O(n) $
- 常数中等
- 代码较长
窝三个月之前还会,现在就不会了
- 后缀自动机遍历
- 时间复杂度 \(O(n*\Sigma)\) , 空间复杂度 \(O(n*\Sigma)\)
- 或者时间复杂度 \(O(n \log n)\) , 空间复杂度 \(O(n)\) (map实现)
- 常数中等
- 代码较长
- 但是并没有倍增好写
倍增算法
放两个链接:
height
记 \(S_i\) 表示 \(S\) 的第 \(i\) 个后缀.
定义
\[height(i) = lcp(S_{sa(i)}, S_{sa(i-1)})
\]
有
\[height(rk(i)) \ge height(rk(i-1)) - 1
\]
代码
#define rep(i,l,r) for(register int i=(l);i<=(r);++i)
#define repdo(i,l,r) for(register int i=(l);i>=(r);--i)
const int ssz=1e6+5;
char s[ssz];
struct tsa{
int n,sigma,t1[ssz],t2[ssz],sa[ssz],c[ssz],*rk=t1,*tp=t2;
int hi[ssz];
int l2n[ssz],stt[21][ssz];
void pr(){
rep(i,1,n)printf("%d %d %d\n",sa[i],rk[i],tp[i]);
}
//from http://www.cnblogs.com/zwfymqz/p/8413523.html
//sa[i]: 长度为w的后缀中, 排名为i的后缀的位置
//rk[i]: 长度为w的后缀中, 从第i个位置开始的后缀的排名
//tp[i]: 长度为2w的后缀中, 第二关键字排名为i的后缀的位置
void rsort(){//sort i([1,n]) by (rk[i],inv_tp[i])
rep(i,0,sigma)c[i]=0;
rep(i,1,n)++c[rk[i]];
rep(i,1,sigma)c[i]+=c[i-1];
repdo(i,n,1)sa[c[rk[tp[i]]]--]=tp[i];
}
void suffixsort(char *s){
rep(i,1,n)rk[i]=s[i],tp[i]=i;
rsort();
for(int w=1,p=0;p<n;sigma=p,w<<=1){
p=0;
rep(i,1,w)tp[++p]=n-w+i; //不存在后w个字符的串的第二关键字最小
rep(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w; //前w个字符不会作为第二关键字
rsort();
//calc rk (|第一关键字|=2w)
swap(tp,rk);//del tp; tp=rk(|第一关键字|=w)
p=1,rk[sa[1]]=p;
rep(i,2,n){
rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
}
}
}
void gethi(char *s){
int k=0;
rep(i,1,n){
if(k>0)--k;
while(s[i+k]==s[sa[rk[i]-1]+k])++k;
hi[rk[i]]=k;
}
}
void rmq(){
int l=0;
rep(i,1,n)stt[0][i]=hi[i],l2n[i]=(i==(1<<(l+1))?++l:l);
rep(i,1,l2n[n]){
repdo(j,n-(1<<i)+1,1){
stt[i][j]=min(stt[i-1][j],stt[i-1][j+(1<<(i-1))]);
}
}
}
int lcp(int x,int y){//x&y are ranks
if(x==y)return n-sa[x]+1;
if(x>y)swap(x,y);
++x;
int l=l2n[y-x+1];
return min(stt[l][x],stt[l][y-(1<<l)+1]);
}
int lcp1(int x,int y,int l1,int l2){
return min(lcp(x,y),min(l1,l2));
}
//O(n \log n + sigma)
void init(char *s,int n0,int sig0){
sigma=sig0,n=n0;
suffixsort(s);
gethi(s);
rmq();
}
}sa;
//init e.g.
sig0=27;
rep(i,1,n)s[i]-='a'-1;
a.init(s,n,sig0);
for copying
const int ssz=1e6+5;
char s[ssz];
struct tsa{
int n,sigma,t1[ssz],t2[ssz],sa[ssz],c[ssz],*rk=t1,*tp=t2;
int hi[ssz];
int l2n[ssz],stt[21][ssz];
void pr(){
rep(i,1,n)printf("%d %d %d\n",sa[i],rk[i],tp[i]);
}
void rsort(){
rep(i,0,sigma)c[i]=0;
rep(i,1,n)++c[rk[i]];
rep(i,1,sigma)c[i]+=c[i-1];
repdo(i,n,1)sa[c[rk[tp[i]]]--]=tp[i];
}
void suffixsort(char *s){
rep(i,1,n)rk[i]=s[i],tp[i]=i;
rsort();
for(int w=1,p=0;p<n;sigma=p,w<<=1){
p=0;
rep(i,1,w)tp[++p]=n-w+i;
rep(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
rsort();
swap(tp,rk);
p=1,rk[sa[1]]=p;
rep(i,2,n){
rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
}
}
}
void gethi(char *s){
int k=0;
rep(i,1,n){
if(k>0)--k;
while(s[i+k]==s[sa[rk[i]-1]+k])++k;
hi[rk[i]]=k;
}
}
void rmq(){
int l=0;
rep(i,1,n)stt[0][i]=hi[i],l2n[i]=(i==(1<<(l+1))?++l:l);
rep(i,1,l2n[n]){
repdo(j,n-(1<<i)+1,1){
stt[i][j]=min(stt[i-1][j],stt[i-1][j+(1<<(i-1))]);
}
}
}
int lcp(int x,int y){//x&y are ranks
if(x==y)return n-sa[x]+1;
if(x>y)swap(x,y);
++x;
int l=l2n[y-x+1];
return min(stt[l][x],stt[l][y-(1<<l)+1]);
}
int lcp1(int x,int y,int l1,int l2){
return min(lcp(x,y),min(l1,l2));
}
void init(char *s,int n0,int sig0){
sigma=sig0,n=n0;
suffixsort(s);
gethi(s);
rmq();
}
}sa;
SA-IS
说不定什么时候窝就回来填坑了