后缀数组

发布时间 2023-06-14 13:47:22作者: shirlybabyyy

后缀树和后缀数组

讲解:

https://blog.csdn.net/weixin_30790841/article/details/96620579?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1

https://blog.csdn.net/yxuanwkeith/article/details/50636898

上面两个参考一下

https://www.bilibili.com/video/BV1qF411G7u5?spm_id_from=333.999.0.0&vd_source=23dc8e19d485a6ac47f03f6520fb15c2

董老师的视频、《算法竞赛》的代码 hdu 1403

里面的三个数组sa[],rak[],height[],其实应用方面主要使用的是height[]数组,加上根据题目来的check函数,二分答案,得到结果

sa[]  后缀数组,sa[i]表示排序为i的后缀编号、rk[i]名次数组,表示后缀i的排名   height[i]高度数组,表示lcp(sa[i],sa[i-1])

1、利用倍增法和桶排序,计算sa数组

  倍增法:串的宽度成倍增加,通过偏移量获取桶号             桶排序:同则入桶,不同则加桶。正序存放,逆序拿取

  排序过程中先排第二关键字、再排第一关键字,然后就统计桶的个数,达到m=n的时候就可以停止了

2、利用sa[]、rk[]计算出height数组

  rk[sa[i]]=i

  计算height过程中,利用了定理height[rk[i]]>=height[rk[i-1]]-1  这个定理,可以不用暴力枚举,利用已经计算好的结果,加速新的结果计算

  这样while过程中,k的加减不会超过n次,所以最多跑2n次

 

模板:luogu3809

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define seed 13131
#define maxn 2000010
using namespace std;
typedef unsigned long long ull;
char s[maxn];
int n,m; //n为后缀个数,m为桶的个数
int x[maxn],y[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
// 桶数组x[i],辅助数组y[i],计数数组c[i]
void get_sa(){  //求后缀数组 
	
	//按照第一个字母排序
	for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
	for(int i=1;i<=m;i++) c[i]+=c[i-1];
	for(int i=n;i;i--) sa[c[x[i]]--]=i;
	//开始基数排序
	for(int k=1;k<=n;k<<=1){
		//按照第二关键字排序
		memset(c,0,sizeof(c));
		for(int i=1;i<=n;i++) y[i]=sa[i];
		for(int i=1;i<=n;i++) c[x[y[i]+k]]++; //偏移量k
		for(int i=1;i<=m;i++) c[i]+=c[i-1];
		for(int i=n;i;i--) sa[c[x[y[i]+k]]--]=y[i];
		 //按照第一关键字排序
		 memset(c,0,sizeof(c));
		 for(int i=1;i<=n;i++) y[i]=sa[i];
		 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;i--) sa[c[x[y[i]]]--]=y[i];
		 //把后缀放入桶数组
		 for(int i=1;i<=n;i++) y[i]=x[i];
		 int i;
		 for(m=0,i=1;i<=n;i++){
		 	if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=m;
		 	else x[sa[i]]=++m;
		 	if(m==n) break;
		 } 
	} 
}
void get_height(){
	for(int i=1;i<=n;i++) rk[sa[i]]=i;
	for(int i=1,k=0;i<=n;i++){  //枚举后缀i
		if(rk[i]==1) continue; //第一名height为0
		if(k) k--; //上一个后缀的height值减1
		int j=sa[rk[i]-1]; //找出后缀i前邻后缀j 
 		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
 		height[rk[i]]=k;
	}
}
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);m=122;
	get_sa();
	get_height();
	for(int i=1;i<=n;i++) printf("%d ",sa[i]);
	return 0;
}

hdu 1403

最长公共子串,和最长重复子串类似,合并s1和s2,得到一个大字符串S,注意中间用‘$’分隔,避免产生更长的子串

首先计算height[]数组,查找最大的height[],然后判断他对应的sa[i]和sa[i-1]对应于大字符串的两端

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define seed 13131
#define maxn 200010
using namespace std;
typedef unsigned long long ull;
char s[maxn];
int sa[maxn],rk[maxn],height[maxn],t1[maxn],cnt[maxn],t2[maxn];
int n;
void calc_sa(){
	int m=127;
	int i,*x=t1,*y=t2;
	for(i=0;i<m;i++) cnt[i]=0;
	for(i=0;i<n;i++) cnt[x[i]=s[i]]++;
	for(i=1;i<m;i++) cnt[i]+=cnt[i-1];
	for(i=n-1;i>=0;i--) sa[--cnt[x[i]]]=i; //
	for(int k=1;k<=n;k<<=1){
		int p=0;
		for(i=n-k;i<n;i++) y[p++]=i;
		for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
		//
		for(i=0;i<m;i++) cnt[i]=0;
		for(i=0;i<n;i++) cnt[x[y[i]]]++;
		for(i=1;i<m;i++) cnt[i]+=cnt[i-1];
		for(i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]]=y[i];
		swap(x,y);
		p=1;x[sa[0]]=0;
		for(i=1;i<n;i++){
			x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i-1]+k]==y[sa[i]+k]? p-1:p++;
			
		}
		if(p>=n) break;
		m=p;
	}
}
void get_hei(int n){
	int i,j,k=0;
	for(i=0;i<n;i++) rk[sa[i]]=i;
	for(i=0;i<n;i++){
		if(k) k--;
		int j=sa[rk[i]-1];
		while(s[i+k]==s[j+k]) k++;
		height[rk[i]]=k;
	}
}
int main(){
	int len1,ans;
	while(scanf("%s",s)!=EOF){
		n=strlen(s);
		len1=n;
		s[n]='$';
		scanf("%s",s+n+1);
		n=strlen(s);
		calc_sa();
		get_hei(n);
		ans=0;
//		for(int i=0;i<n;i++){
//			printf("sa[i]=%d  rk[i]=%d\n",sa[i],rk[i]);
//		}
//		printf("\n");
//		for(int i=0;i<n;i++) printf("%d    ",height[i]);
		for(int i=1;i<n;i++){
			if(height[i]>ans&&((sa[i-1]<len1&&sa[i]>=len1)||(sa[i-1]>=len1&&sa[i]<len1))) ans=height[i];
		}
		printf("%d\n",ans);
	}
	return 0;
}

  

 

 

 

一些例题:  https://blog.csdn.net/qq_36038511/article/details/78133190

POJ 1743 Musical Theme

不可重叠最长重复子串
题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。“主题”是整个音符序列的一个子串,它需要满足如下条件:
1.长度至少为5个音符。
2.在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)
3.重复出现的同一主题不能有公共部分。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=21010;
const int INF=0x3fffffff;
typedef long long LL;
/*
caioj1467: 后缀数组1:不可重叠最长重复子串
题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。“主题”是整个音符序列的一个子串,它需要满足如下条件: 
1.长度至少为5个音符。 
2.在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值) 
3.重复出现的同一主题不能有公共部分。
原文链接:https://blog.csdn.net/qq_36038511/article/details/78133190
*/ 
int a[maxn],tt[maxn];
char ss[maxn];
int rak[maxn],sa1[maxn],sa2[maxn];
int rsort[maxn];
void get_sa(int n,int m){
	memcpy(rak,a,sizeof(rak));
	memset(rsort,0,sizeof(rsort));
	for(int i=1;i<=n;i++) rsort[rak[i]]++;
	for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
	for(int i=n;i>=1;i--) sa1[rsort[rak[i]]--]=i;
	int ln=1,p=0;
	while(p<n){
		int k=0;
		for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
		for(int i=1;i<=n;i++) if(sa1[i]-ln>0) sa2[++k]=sa1[i]-ln;
		//第二关键字排序 
		memset(rsort,0,sizeof(rsort));
		for(int i=1;i<=n;i++) rsort[rak[i]]++;
		for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
		for(int i=n;i>=1;i--) sa1[rsort[rak[sa2[i]]]--]=sa2[i];
		for(int i=1;i<=n;i++) tt[i]=rak[i];  //tt辅助数组 
		p=1;
		rak[sa1[1]]=1;
		for(int i=2;i<=n;i++){
			if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
			rak[sa1[i]]=p;
		}
		m=p;
		ln*=2;
	}
} 
//height[i]:sa[i]和sa[i-1]的最长公共前缀的长度
    /*
    定义h[i]=height[rank[i]],
    h数组有以下性质: h[i]≥h[i-1]-1
 证明:
 (suffix:后缀)
设suffix(k)是排在suffix(i-1)前一名的后缀,则它们的最长公共前缀是h[i-1]。那么同时++,suffix(k+1)将排在suffix(i)的前面(这里一定h[i-1]>1,如果h[i-1]≤1,上面的原式显然成立)并且suffix(k+1)和suffix(i)的最长公共前缀是h[i-1]-1(因为同时往后挪了一位,故-1),所以suffix(i)和在它前一名的后缀的最长公共前缀至少是h[i-1]-1。因此得证。
   实现的时候其实没有必要保存h数组,只须按照h[1],h[2],……,h[n]的顺序计算即可。
    */
int height[maxn*10];
void get_he(int n){ //主要是这个问题
	 int j,k=0;
	 for(int i=2;i<=n;i++){
	 	j=sa1[rak[i]-1];  //前一位 
	 	if(k!=0) k--;  //保证>0
	    while(a[j+k]==a[i+k]) k++;  //暴力询问
		height[rak[i]]=k; 
	 }
}
bool check(int k,int n){  //检查有没有重叠 
	for(int i=2;i<=n;i++){
		if(height[i]>=k){
			for(int j=i-1;j>=2;j--){
				if(abs(sa1[i]-sa1[j])>=k) return 1;
				if(height[j]<k) break;
			}
		}
	}
	return false;
}
int main(){
	int n;
	while(scanf("%d",&n)!=EOF){
		if(n==0) break;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		int mmax=-9999999;
		for(int i=1;i<n;i++){
			a[i]=a[i+1]-a[i]+88;
			if(mmax<a[i]) mmax=a[i];
		}
		a[n]=0;
		n--;
		get_sa(n,mmax);
		get_he(n);
		int l=1,r=n,ans=1;
		while(l<=r){  //二分答案 
			int mid=(l+r)/2;
			if(check(mid,n)==true){
				ans=mid;
				l=mid+1;
			}
			else r=mid-1;
		}
		if(ans<4) printf("0\n");
		else printf("%d\n",ans+1);
	}
return 0;
}

后缀数组2:可重叠的k次最长重复子串
【问题描述】
农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天 产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。 John的牛奶按质量可以被赋予一个0到1000000之间的数。并且John记录了N(1<=N<=20000)天的 牛奶质量值。他想知道最长的出现了至少K(2<=K<=N)次的模式的长度。 比如1 2 3 2 3 2 3 1 中 2 3 2 3出现了两次。当K=2时,这个长度为4。(可重叠的k次最长重复子串)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1100000;
const int INF=0x3fffffff;
typedef long long LL;
/*
caioj1468: 后缀数组2:可重叠的k次最长重复子串
【问题描述】 
农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天 产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。
 John的牛奶按质量可以被赋予一个0到1000000之间的数。并且John记录了N(1<=N<=20000)天的 牛奶质量值。他想知道最长的出现了至少K(2<=K<=N)次的模式的长度。 比
 如1 2 3 2 3 2 3 1 中 2 3 2 3出现了两次。当K=2时,这个长度为4。(可重叠的k次最长重复子串)

*/
int tt[21000],a[21000],sa1[21000],sa2[21000],rak[21000];
int rsort[maxn],height[21000];
void get_sa(int n,int m){
	memcpy(rak,a,sizeof(rak));
	memset(rsort,0,sizeof(rsort));
	for(int i=1;i<=n;i++) rsort[rak[i]]++;
	for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
	for(int i=n;i>=1;i--) sa1[rsort[rak[i]]--]=i;
	int ln=1,p=0;
	while(p<n){
		int k=0;
		for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
		for(int i=1;i<=n;i++) if(sa1[i]>ln) sa2[++k]=sa1[i]-ln;
		memset(rsort,0,sizeof(rsort));
		for(int i=1;i<=n;i++)  rsort[rak[i]]++;
		for(int i=1;i<=m;i++)  rsort[i]+=rsort[i-1];
		for(int i=n;i>=1;i--)  sa1[rsort[rak[sa2[i]]]--]=sa2[i];
		memcpy(tt,rak,sizeof(rak));
		p=1;
		rak[sa1[1]]=1;
		for(int i=2;i<=n;i++){
			if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
			rak[sa1[i]]=p;
		}
		m=p;
		ln*=2;
	} 
}
void get_he(int n){
	int j,k=0;
	for(int i=1;i<=n;i++){
		j=sa1[rak[i]-1];
		if(k) k--;
		while(a[i+k]==a[j+k]) k++;
		height[rak[i]]=k;
	}
}
int N,K;
bool check(int x,int n){  //二分检查 
	int tt=1;
	for(int i=2;i<=n;i++){
		if(height[i]>=x){
			tt++;
			if(tt==K) return true;
		}
		else tt=1;
	}
	return false;
}

int main(){
	while(scanf("%d %d",&N,&K)!=EOF){
		int maxx=0;
		if(N==0) break;
		for(int i=1;i<=N;i++){
			scanf("%d",&a[i]);
			maxx=max(maxx,a[i]);
		}
		get_sa(N,maxx);
		get_he(N);
		int l=1,r=N,ans=0;
		while(l<=r){
			int mid=(l+r)/2;
			if(check(mid,N)) {
				ans=mid;
				l=mid+1;
			}
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
return 0;
}

caioj1469: 后缀数组3:连续重复子串

【问题描述】 
求两个字符串的最长公共子串。(长度不超过100000)

把两个字符串接在一起,然后在中间插入一个从没有出现过的字符 
注意判断找到的公共子串会不会在同一个字符串内

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=510000;
const int INF=0x3fffffff;
typedef long long LL;
//求两个字符串的最长公共子串
//把两个字符串接在一起,然后在中间插入一个从没有出现过的字符 
//注意判断找到的公共子串会不会在同一个字符串内 
int a[maxn],rak[maxn],rsort[maxn],sa1[maxn],sa2[maxn];
char s1[210000],s2[210000];
int tt[maxn],height[maxn];
void get_sa(int n,int m){
	for(int i=1;i<=n;i++) rak[i]=a[i];
	memset(rsort,0,sizeof(rsort));
	for(int i=1;i<=n;i++) rsort[rak[i]]++;
	for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
	for(int i=n;i>=1;i--) sa1[rsort[rak[i]]--]=i;
	
	int p=0,ln=1;
	while(p<n){
		int k=0;
		for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
		for(int i=1;i<=n;i++) if(sa1[i]>ln) sa2[++k]=sa1[i]-ln;
		
		memset(rsort,0,sizeof(rsort));
		for(int i=1;i<=n;i++) rsort[rak[i]]++;
		for(int i=1;i<=m;i++) rsort[i]+=rsort[i-1];
		for(int i=n;i>=1;i--) sa1[rsort[rak[sa2[i]]]--]=sa2[i];
		
		for(int i=1;i<=n;i++) tt[i]=rak[i];
		p=1;rak[sa1[1]]=1;
		for(int i=2;i<=n;i++){
		if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
		rak[sa1[i]]=p;
		}
		m=p;
		ln*=2;
	}
	a[0]=0;sa1[0]=0;
}
void get_he(int n){
	int k=0;
	for(int i=1;i<=n;i++){
		int j=sa1[rak[i]-1];
		if(k) k--;
		while(a[i+k]==a[j+k]) k++;
		height[rak[i]]=k;
	}
}
int main(){
	int n,maxx=0;
	scanf("%s",s1+1);
	int lena=strlen(s1+1);
	scanf("%s",s2+1);
	int lenb=strlen(s2+1);
	for(int i=1;i<=lena;i++){
		a[i]=s1[i];
		if(maxx<a[i]) maxx=a[i];
	}
	a[lena+1]='$';
	n=lena+lenb+1;
	int pp=0;
	for(int i=lena+2;i<=n;i++){
		a[i]=s2[++pp];
		if(maxx<a[i]) maxx=a[i];
	}
	get_sa(n,maxx);
	get_he(n);
	int ans=0;
	//能更大,也能保证不在同一个串里面 
	for(int i=2;i<=n;i++){
		if(ans<height[i]&&((sa1[i]<=lena&&sa1[i-1]>lena+1)||(sa1[i]>lena+1&&sa1[i-1]<=lena))) ans=height[i];
	}
	printf("%d\n",ans);
return 0;
}

 

caioj1470: 后缀数组4:Life Forms
【问题描述】
求n个字符串(长度1000)的最长的一个子串,满足该子串在一半以上的字符串中出现过,并输出该子串,如果有多个子串满足要求,则按字典序输出所有的子串;

把所有字符串都扔在一起用从未出现过的字符隔开,
然后判断在所有字符串中都出现过的字符串有多少种,
最后输出

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[1110000],kinds[1110000],Rank[1110000],Rsort[111000],sa1[1110000],sa2[1110000],tt[1110000],height[1110000];
char s[110000];
void get_sa(int n,int m)
{
    for(int i=1;i<=n;i++) Rank[i]=a[i];
    memset(Rsort,0,sizeof(Rsort));
    for(int i=1;i<=n;i++) Rsort[Rank[i]]++;
    for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];
    for(int i=n;i>=1;i--) sa1[Rsort[Rank[i]]--]=i;

    int p=0,ln=1;
    while(p<n)
    {
        int k=0;
        for(int i=n-ln+1;i<=n;i++) sa2[++k]=i;
        for(int i=1;i<=n;i++) if(sa1[i]-ln>0) sa2[++k]=sa1[i]-ln;

        memset(Rsort,0,sizeof(Rsort));
        for(int i=1;i<=n;i++) Rsort[Rank[i]]++;
        for(int i=1;i<=m;i++) Rsort[i]+=Rsort[i-1];
        for(int i=n;i>=1;i--) sa1[Rsort[Rank[sa2[i]]]--]=sa2[i];

        for(int i=1;i<=n;i++) tt[i]=Rank[i];
        p=1;Rank[sa1[1]]=1;
        for(int i=2;i<=n;i++)
        {
            if(tt[sa1[i]]!=tt[sa1[i-1]]||tt[sa1[i]+ln]!=tt[sa1[i-1]+ln]) p++;
            Rank[sa1[i]]=p;
        }
        m=p;ln*=2;
    }
    a[0]=0;sa1[0]=0;
}
void get_height(int n)
{
    int k=0;
    for(int i=1;i<=n;i++)
    {
        int j=sa1[Rank[i]-1];
        if(k) k--;
        while(a[i+k]==a[j+k]) k++;
        height[Rank[i]]=k;
    }
}
int stlen=0,v[210],start[1110000];
bool check(int k,int n,int nn)
{
    int ks=0,kind=0,stl=0;
    for(int i=1;i<=n;i++)
    {
        if(height[i]<k) 
        {
            if(ks>nn/2)
            {
                stl++;
                start[stl]=sa1[i-1];
            }
            memset(v,0,sizeof(v));
            ks=0;
        }
        kind=kinds[sa1[i]];
        if(v[kind]==0&&kind>0)
        {
            v[kind]=1;ks++;
        }
    }
    if(ks>nn/2)
    {
        stlen++;
        start[stl]=sa1[n];
    }
    if(stl) {stlen=stl;return true;}
    return false;
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        stlen=0;
        if(n==0) break;
        int maxx=500,st=0;
        for(int i=1;i<=n;i++) 
        {
            scanf("%s",s+1);
            int len=strlen(s+1);
            for(int j=1;j<=len;j++)
            {
                a[j+st]=s[j];
                kinds[j+st]=i;
            }
            a[len+st+1]=i+400;
            kinds[len+st+1]=0;
            st+=len+1;
        }
        get_sa(st,maxx);
        get_height(st);
        int l=1,r=1100,mid,len=0;
        while(l<=r)
        {
            mid=(l+r)/2;
            if(check(mid,st,n))
            {
                len=mid;l=mid+1;
            }
            else r=mid-1;
        }
        for(int i=1;i<=stlen;i++)
        {
            for(int j=1;j<=len;j++) printf("%c",a[j+start[i]-1]);
            printf("\n");
        }
        if(stlen==0) printf("?\n");
        printf("\n");
    }
}

  

一些其他的应用

Q1:一个串中两个串的最大公共前缀是多少?
A1:这不就是Height吗?用rmq预处理,再O(1)查询。

Q2:一个串中可重叠的重复最长子串是多长?
A2:就是求任意两个后缀的最长公共前缀,而任意两个后缀的最长公共前缀都是Height 数组里某一段的最小值,那最长的就是Height中的最大值。

Q3:一个串中不可重叠的重复最长子串是多长?
A3:先二分答案,转化成判别式的问题比较好处理。假设当前需要判别长度为k是否符合要求,只需把排序后的后缀分成若干组,其中每组的后缀之间的Height 值都不小于k,再判断其中有没有不重复的后缀,具体就是看最大的SA值和最小的SA值相差超不超过k,有一组超过的话k就是合法答案。

A4:一个字符串不相等的子串的个数是多少?
Q4:每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。而且可以发现每一个后缀Suffix[SA[i]]的贡献是Len - SA[i] + 1,但是有子串算重复,重复的就是Heigh[i]个与前面相同的前缀,那么减去就可以了。最后,一个后缀Suffix[SA[i]]的贡献就是Len - SA[k] + 1 - Height[k]。
对于后缀数组更多的应用这里就不详细阐述,经过思考后每个人都会发现它的一些不同的用途,它的功能也许比你想象中的更强大!