前言
后綴數組還是很難理解的,所以直接背個板子就好了。——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