后缀数组一直是字符串中重点的算法
后缀数组的一般求法是倍增,时间复杂度是
但是有的时候,这样的算法复杂度及常数不能满足题目要求
那我们就来学习一种线性的构造方法:DC3
算法的过程是这样的,首先我们把后缀分成两类,一类是下标是三的倍数的,另一类则不是。
我们先处理下标不是
的这一类,比如
这个字符串(下标从
开始),令
表示以
下标开头的后缀,则两类分别是:
第一类
第二类
我们将
先各自在串后面补比整个串小的字符使长度为3的倍数,然后连起来,形成一个新串,如
然后我们发现每一个原来要排序的串等价于这个新串的
这样的话我们就先把这个串变成一个三元组串,即
、
、
、
、
我们可以先基数排序,然后又转化成了一个sa问题
接下来我们处理第二类
我们发现每个后缀可以转化为一个字符和一个已知的后缀,如
,
(
表示
的第
个字符)
我们再基数排序就得到第二类的排名
最后我们归并
考虑分类讨论第一类后缀:
如果一个
的后缀比较一个第二类后缀,那么两类后缀均可以转化为
,可以比较
如果一个
的后缀比较一个第二类后缀,那么两类后缀均可以转化为
,也可以比较
所以就做完了!
时间复杂度的话,可以通过等比数列求和等方法推出
最后附上莫名其妙写了
行的uoj模板
#include<iostream>
#include<cstring>
#include<cassert>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<time.h>
#include<algorithm>
using namespace std;
#define REP(i,x,y) for(int i=x;i<=y;i++)
#define rep(i,n) REP(i,1,n)
#define rep0(i,n) REP(i,0,n-1)
#define repG(i,x) for(int i=pos[x];~i;i=e[i].next)
#define ll long long
#define db double
const int N=1e6+7;
const int INF=1e9+7;
char ss[N];
int d[N];
int n;
struct SA{
int ra[N*4],sa[N*4],a[N*4],p[N],rk[N],sk[N],ct[N],ls[N],nw[N],dr[N],ds[N],gs[N],gr[N],u[N];
bool cmp1(int x1,int y1,int x2,int y2){
if(x1!=x2)return x1<x2;
return y1<y2;
}
bool cmp2(int x1,int y1,int z1,int x2,int y2,int z2){
if(x1!=x2)return x1<x2;
if(y1!=y2)return y1<y2;
return z1<z2;
}
void sor(int L,int d,int o){//基数排序
rep0(i,o+2)ct[i]=ls[i]=nw[i]=0;
rep(i,L){
nw[a[p[sk[i]]+d]+1]++;
if(ct[a[p[sk[i]]+d]+1]!=rk[sk[i]]){
ct[a[p[sk[i]]+d]+1]=rk[sk[i]];
ls[a[p[sk[i]]+d]+1]=nw[a[p[sk[i]]+d]+1];
}
ds[sk[i]]=nw[a[p[sk[i]]+d]+1];
dr[sk[i]]=ls[a[p[sk[i]]+d]+1];
}
rep(i,o+1)nw[i]+=nw[i-1];
rep(i,L){
sk[nw[a[p[i]+d]]+ds[i]]=i;
rk[i]=nw[a[p[i]+d]]+dr[i];
}
}
void build(int l,int r){
int len=r-l+1,cnt=0,c1=0,h1=1,h2=1,h3=l;
if(len==1){//边界
sa[l]=l;
ra[l]=1;
return;
}
REP(i,l,r){
if((i-l)%3==0)continue;
p[++cnt]=i;
rk[cnt]=1;
sk[cnt]=cnt;
}
for(int i=2;~i;i--)sor(cnt,i,len);
bool fl=0;
rep(i,cnt-1)if(rk[sk[i]]==rk[sk[i+1]]){fl=1; break;}
if(fl==1){//没排好再递归
rep(i,cnt){
if(i&1)a[r+4+i/2]=rk[i]+1;
else a[r+4+(cnt+1)/2+i/2]=rk[i]+1;
}
a[r+4+(cnt+1)/2]=1;//中间记得添加分割
build(r+4,r+cnt+4);
rep(i,cnt){
if(i&1)rk[i]=ra[r+4+i/2]-1;
else rk[i]=ra[r+4+(cnt+1)/2+i/2]-1;
}
rep(i,cnt)sk[rk[i]]=i;
cnt=0;
REP(i,l,r)if((i-l)%3!=0)p[++cnt]=i;
}
rep0(i,cnt+1)nw[i]=0;
rep(i,cnt){
u[i]=p[i];
gs[i]=sk[i];
gr[i]=rk[i];
if(i&1)nw[rk[i]]++;
}
if(r>u[cnt])nw[0]++;
rep(i,cnt)nw[i]+=nw[i-1];
rep(i,cnt){
if(i&1){
p[++c1]=p[i]-1;
rk[c1]=nw[rk[i]];
sk[rk[c1]]=c1;
}
}
if(r>u[cnt]){
p[++c1]=r;
rk[c1]=1;
sk[1]=c1;
}
sor(c1,0,len);//排序第二部分
rep(i,cnt)ct[u[i]]=i;
while(h1<=cnt&&h2<=c1){
bool cmp;
int v1=u[gs[h1]],v2=p[sk[h2]];//最冗长的归并
if((v1-l)%3==1){
int u1=(v1==r)?0:gr[ct[v1+1]],u2=(v2==r)?0:gr[ct[v2+1]];
cmp=cmp1(a[v1],u1,a[v2],u2);
}
else{
int u1=(v1+2>r)?0:gr[ct[v1+2]],u2=(v2+2>r)?0:gr[ct[v2+2]];
cmp=cmp2(a[v1],a[v1+1],u1,a[v2],a[v2+1],u2);
}
if(cmp)sa[h3++]=u[gs[h1++]];
else sa[h3++]=p[sk[h2++]];
}
while(h1<=cnt)sa[h3++]=u[gs[h1++]];
while(h2<=c1)sa[h3++]=p[sk[h2++]];
REP(i,l,r)ra[sa[i]]=i-l+1;
}
int ht[N];
void bht(){//求height数组
int ans=0;
rep(i,n){
if(ra[i]==n){ans=0; continue;}
int p=sa[ra[i]+1];
if(ans)ans--;
while(a[p+ans]==a[i+ans])ans++;
ht[ra[i]]=ans;
}
}
}T;
int main(){
scanf("%s",ss+1);
n=strlen(ss+1);
rep(i,n)d[ss[i]]++;
rep(i,128)d[i]+=d[i-1];
rep(i,n)T.a[i]=d[ss[i]];
T.build(1,n);
T.bht();
rep(i,n)printf("%d ",T.sa[i]);
puts("");
rep(i,n-1)printf("%d ",T.ht[i]);
puts("");
return 0;
}
我想没什么人会看到这吧
但是我以后一定会把模板写的更短更好看,常数更小
我好像跑不过倍增诶