定義
在字符串處理當中,後綴樹和後綴數組都是非常有力的工具,其中後綴樹大家瞭解得比較多,關於後綴數組則很少見於國內的資料。其實後綴數組是後綴樹的一個非常精巧的替代品,它比後綴樹容易編程實現,能夠實現後綴樹的很多功能而時間複雜度也不太遜色,並且,它比後綴樹所佔用的空間小很多。可以說,在信息學競賽中後綴數組比後綴樹要更爲實用。
變量定義
Suffix(i):表示S[i..len(S)],即從第i位開始的字符串後綴。
SA數組:後綴數組SA是一個一維數組,它保存1..n的某個排列SA[1],SA[2],……,SA[n],並保證Suffix(SA[i])<Suffix(SA[i+1]),1≤i<n。也就是將S的n個後綴從小到大進行排序之後把排好序的後綴的開頭位置順次放入SA中。
rank數組:名次數組 Rank[i] 保存的是 Suffix(i) 在所有後綴中從小到大排列的“ 名次 ”後綴數組SA與名次數組Rank的對應關係。簡單的說,後綴數組(SA)是“ 排第幾的是誰?”,名次數組(RANK)是“ 你排第幾?”。容易看出,後綴數組和名次數組爲互逆運算。
height數組:height[i]=LCP(i-1,i),即第rank[i-1]與rank[i]的最長前綴是多長。
2倍增算法
比較容易實現的一種(相對於DC3算法,雖然DC3比較快,但是實現有點複雜,我不會……),一般的題目應該也夠用了,時間複雜度O(n*log n),還算比較快了。
思路
我們倍增一個k,與RMQ的思想差不多,以
假設現在到了第三輪,k爲2,第一關鍵字是上一輪同一位置的rank,第二關鍵字爲上一輪向後k個位置的rank,然後進行雙關鍵字排序(這裏推薦用基數排序,當然快排也行,但是慢一點)。
memset(t,0,sizeof(t));
fo(i,1,n) t[rank[i+k]]++;
fo(i,1,da) t[i]+=t[i-1];
for(i=n;i>0;i--) se[t[rank[i+k]]--]=i;
//第二關鍵字排序
memset(t,0,sizeof(t));
fo(i,1,n) t[rank[i]]++;
fo(i,1,da) t[i]+=t[i-1];
for(i=n;i>0;i--) sa[t[rank[se[i]]]--]=se[i];
//第一關鍵字排序
就這樣可以求出一個SA數組,但是數組之中都是不重複的,我們還要處理一下相同的情況。當S[i]=S[j]&&S[i+k]=S[j+k]時就定義爲相同。
bool cmp(int *a,int x,int y,int z){return a[x]==a[y]&&a[x+z]==a[y+z];}
j=1;zs[sa[1]]=1;
fo(i,2,n)
zs[sa[i]]=cmp(rank,sa[i],sa[i-1],k)?j:++j;
求完了rank和SA數組後,就要求height數組,通過這個數組的一個特殊性質:h[i]≥h[i-1]-1,我們可以把複雜度降至
i=j=k=0;
for(int i=1;i<=n;h[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
模板題
文件修復(JZOJ 1598)
//快速排序
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+5;
struct arr{
int x,y,bh;
}t[N];
char s[N];
int n,i,j,k,rank[N],sa[N],ws[N],a[N],h[N],num,ans;
int cmp(arr x,arr y){return x.x<y.x||(x.x==y.x&&x.y<y.y);}
void deal(){
fo(i,1,n) rank[i]=a[i];
for(k=0;k<=n;){
fo(i,1,n){
t[i].x=rank[i],t[i].bh=i;
if(i+k>n) t[i].y=0;else t[i].y=rank[i+k];
}
sort(t+1,t+n+1,cmp);
rank[t[1].bh]=1;sa[1]=t[1].bh;num=1;
fo(i,2,n){
if(t[i-1].x!=t[i].x||t[i-1].y!=t[i].y) num++;
rank[t[i].bh]=num;
sa[i]=t[i].bh;
}
k*=2;
if(k==0) k=1;
}
i=j=k=0;
for(int i=1;i<=n;h[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
}
int main(){
scanf("%s",s);
n=strlen(s);
fo(i,1,n) a[i]=s[i-1];
deal();
ans=h[2];
fo(i,3,n) ans+=max(0,h[i]-h[i-1]);
printf("%d",ans);
}
還有一種快一點
//基數排序
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+5;
char s[N];
int n,i,j,k,rank[N],sa[N],zs[N],se[N],t[N],a[N],h[N],num,ans,da;
bool cmp(int *a,int x,int y,int z){return a[x]==a[y]&&a[x+z]==a[y+z];}
void deal(){
fo(i,1,n) rank[i]=a[i],se[i]=i;
for(k=0;k<=n;){
memset(t,0,sizeof(t));
fo(i,1,n) t[rank[i+k]]++;
fo(i,1,da) t[i]+=t[i-1];
for(i=n;i>0;i--) se[t[rank[i+k]]--]=i;
memset(t,0,sizeof(t));
fo(i,1,n) t[rank[i]]++;
fo(i,1,da) t[i]+=t[i-1];
for(i=n;i>0;i--) sa[t[rank[se[i]]]--]=se[i];
j=1;zs[sa[1]]=1;
fo(i,2,n)
zs[sa[i]]=cmp(rank,sa[i],sa[i-1],k)?j:++j;
swap(zs,rank);
if(k==0) k=1;else k*=2;
}
i=j=k=0;
for(int i=1;i<=n;h[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
}
int main(){
scanf("%s",s);
n=strlen(s);
fo(i,1,n) a[i]=s[i-1],da=max(da,a[i]);
da=max(n,1000);
deal();
ans=h[2];
fo(i,3,n) ans+=max(0,h[i]-h[i-1]);
printf("%d",ans);
}