学习步骤
首先明确几个概念:
sa[i]:即,排名为i的后缀的起点下标
rank[i]:即,第i个后缀的排名
height[i]:即,sa[i]和sa[i-1]的最长公共前缀
h[i]:即,height[rank[i]],即第i个后缀与前一名的最长公共前缀
一些结论
结论一:
定义:
LCP(i,j)=lcp(suffix(sa[i],suffix(sa[j])
L
C
P
(
i
,
j
)
=
l
c
p
(
s
u
f
f
i
x
(
s
a
[
i
]
,
s
u
f
f
i
x
(
s
a
[
j
]
)
则转化:
LCP(i,j)=min(height[k])
L
C
P
(
i
,
j
)
=
m
i
n
(
h
e
i
g
h
t
[
k
]
)
,sa[i]+1≤k≤sa[j]
结论二:
suffix[i]的与其他后缀的最长公共前缀为
max(height[sa[i]],height[sa[i]+1])
m
a
x
(
h
e
i
g
h
t
[
s
a
[
i
]
]
,
h
e
i
g
h
t
[
s
a
[
i
]
+
1
]
)
结论三:
h[i]
h
[
i
]
≥
h[i−1]−1
h
[
i
−
1
]
−
1
,理由suffix[i-1]去掉一个字符后的suffix[i],必然存在另一个后缀也去掉相同字符后的的匹配长度为h[i-1]-1;由结论二可知,
h[i]
h
[
i
]
≥
h[i−1]−1
h
[
i
−
1
]
−
1
;
了解一下后缀排序过程
其中x为上一次排序的rank,而y为第二关键字的rank,两者组合后的排序为本次的rank
poj2774
题意:问两个字符串的最长公共子串
题解:两个字符串拼接,中间用特殊字符隔开,然后后缀数组模版,详见代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=200005;
int a[maxn];//
int sa[maxn],height[maxn],rank[maxn],tax[maxn],y[maxn];
//sa[i]排名为i的后缀的起始下标;rank[i]起始下标为i的后缀的排名;
//height[i]排名为i的后缀和前一名的lcp;tax[i]辅助计数排序,记录的是排名为i的个数,它维护一个前缀
//y[i]维护的是第二关键字 ,表示排名为i的是那个第二关键字与第y[i]个第一关键字组合排序
int n,m;//n为字符串长度,m为ascii码个数,也是排名种数。
void rsort() {
//rank为第一关键字,y为第二关键字
for(int i=0; i<=m; i++)tax[i]=0;
for(int i=1; i<=n; i++)tax[rank[y[i]]]++; //用第一关键字对第二关键字的排名进行离散化
for(int i=1; i<=m; i++)tax[i]+=tax[i-1]; //tax[i]表示的是排名
for(int i=n; i>=1; i--)sa[tax[rank[y[i]]]--]=y[i];
}
int cmp(int *f, int x, int y, int w) {
return f[x] == f[y] && f[x + w] == f[y + w];
}
void suffix() {
for(int i=1; i<=n; i++)rank[i]=a[i],y[i]=i;
m=128;
rsort();//rsort后得到本轮排序的sa
for(int w=1,p=1,i; p<n; w+=w,m=p) { //p表示本轮排序的rank总类数,当有n种rank时,即p=n,排序结束
for (p = 0, i = n - w + 1; i <= n; i ++) y[++ p] = i; //长度越界,第二关键字为0
for (i = 1; i <= n; i ++) if (sa[i] > w) y[++ p] = sa[i] - w;
//由计数排序的操作过程可知,sa的前w个后缀在第二关键字中被移除,而后溢出的补0
rsort();
swap(y,rank);//上一轮的rank放到y中保存一下,用于计算下一轮的rank;
rank[sa[1]]=p=1;
for(i=2; i<=n; i++)rank[sa[i]]=cmp(y,sa[i],sa[i-1],w)?p:++p;
//通过观察上一轮的rank,比较当前排名为i和i-1的rank是否一样,如果一样则rank相等,否则新加一个rank
}
//计算lcp
int k=0,j;
for(int i=1; i<=n; height[rank[i++]]=k)
for(k=(k?k-1:k),j=sa[rank[i]-1]; (i+k)<=n&&(j+k)<=n&&a[i+k]==a[j+k]; k++);
//通过我们的结论h[i]>=h[i-1]-1;可知我们设k=h[i-1]-1;那么下一次匹配时只需从 a[i+k]==a[j+k]开始比较
}
char str[maxn],str1[maxn];
int l1,l2;
void init() {
scanf("%s %s",str,str1);
l1=strlen(str);
l2=strlen(str1);
for(int i=1; i<=l1; i++) {
a[i]=str[i-1];
}
a[l1+1]=128;
for(int i=0; i<l2; i++) {
a[i+l1+2]=str1[i];
}
n=l1+l2+1;
}
int main() {
int ans=0;
init();
suffix();
for(int i=2; i<=n; i++) {
if(ans<height[i]) {
int x = min(sa[i], sa[i - 1]);
int y = max(sa[i], sa[i - 1]);
if((x<l1+1)&&(y>l1+1))
ans=max(height[i],ans);
}
}
printf("%d\n",ans);
return 0;
}