后缀数组从入门到省选
一,SA 后缀数组(倍增)
后缀数组 sa[i]
:表示所有后缀在排完序后,排名为i的后缀在原串中的位置。
名次数组rk[i]
:表示所有后缀在排序完后,原字符串中第 i
名现在的排名。
- 基于倍增排序之后,直接转化为双关键词基数排序
- 1,重复使用
- 2,空串保送
- 3,从后遍历保证相对顺序
二,LCP 最长公共前缀
h
e
i
g
h
t
[
i
]
height[i]
height[i]:表示
s
u
f
f
[
s
a
[
i
]
]
suff[sa[i]]
suff[sa[i]]和
s
u
f
f
[
s
a
[
i
−
1
]
]
suff[sa[i−1]]
suff[sa[i−1]]的最大公共前缀,也就是排名完后两个相邻的后缀的最长公共前缀。
h
[
i
]
h[i]
h[i]:等于
h
e
i
g
h
t
[
r
a
n
k
[
i
]
height[rank[i]
height[rank[i],
s
u
f
f
[
i
]
suff[i]
suff[i]和排序后在它前一名的后缀的最长公共前缀。
定义
L
C
P
(
i
,
j
)
=
l
c
p
(
s
u
f
(
s
a
[
i
]
)
,
s
u
f
(
s
a
[
j
]
)
LCP(i,j)=lcp(suf(sa[i]),suf(sa[j])
LCP(i,j)=lcp(suf(sa[i]),suf(sa[j])。
1,LCP lemma(传递性)
1,对任意
1
≤
i
<
j
<
k
≤
n
,
L
C
P
(
i
,
k
)
=
m
i
n
{
L
C
P
(
i
,
j
)
,
L
C
P
(
j
,
k
)
}
1≤i<j<k≤n,LCP(i,k)=min\{LCP(~i~,~j~),LCP(~j~,~k~)\}
1≤i<j<k≤n,LCP(i,k)=min{LCP( i , j ),LCP( j , k )}
2,设
i
+
1
≤
k
≤
j
,
L
C
P
(
i
,
j
)
=
m
i
n
{
L
C
P
(
k
−
1
,
k
)
}
i+1 \le k \le j,LCP(i,j)=min\{LCP(~k−1~,~k)\}
i+1≤k≤j,LCP(i,j)=min{LCP( k−1 , k)}
- 强调:这里的
i
i
i,
j
j
j,
k
k
k 均指代排名
证明:
正向由定义易知: L C P ( i , k ) > = m i n { L C P ( i , j ) , L C P ( j , k ) } LCP(i,k)>=min\{LCP(i,j),LCP(j,k)\} LCP(i,k)>=min{LCP(i,j),LCP(j,k)}
反向夹逼定理如图
2,LCP Theorem (位序相关性)
定义:
h
(
i
)
=
h
e
i
g
h
t
(
r
k
[
i
]
)
h(i)=height(rk[i])
h(i)=height(rk[i])
性质:
h
(
i
)
≥
h
(
i
−
1
)
−
1
h(i) \ge h(i-1)−1
h(i)≥h(i−1)−1。
证明如图
求法
void SA()
{
for (int i = 1; i <= n; i ++ ) c[x[i]=s[i]]++;
for (int i = 2; i <= m; i ++ ) c[i]+=c[i-1];
for (int i = n; i >= 1; i -- ) sa[c[x[i]]--]=i;
for(int k = 1;k <= n; k<<=1)
{
int num = 0;
for(int i = n-k+1; i <= n; i ++ ) y[++num]=i;
for (int i = 1; i <= n; i ++ )
{
if(sa[i]<=k)continue;
y[++num]=sa[i]-k;
}
for (int i = 1; i <= m; i ++ )c[i]=0;
for (int i = 1; i <= n; i ++ )c[x[i]]++;
for (int i = 2; i <= m; i ++ )c[i]+=c[i-1];
for (int i = n; i >= 1; i -- ) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;
num = 1;
for (int i = 2; i <= n; i ++ )
{
if(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])
{
x[sa[i]] = num;
}
else x[sa[i]] = ++num;
}
if(num==n)break;
else m = num;
}
}
void get_height()
{
for (int i = 1; i <= n; i ++ ) rk[sa[i]] = i;
for (int i = 1, k = 0; i <= n; i ++)
{
if(rk[i]==1)continue;
if(k) k--;
int j = sa[rk[i]-1];
while (i + k <= n && j + k <= n && s[i+k] == s[j+k] )k++;
height[rk[i]] = k;
}
}
三,习题
1,P4248 【[AHOI2013]差异】
求解很多个左端点和右端点都在变的LCP
- 转化为贡献型,对于一个
height[i]
,思考他的贡献对象 - 注意到 lcp lemma,就是区间最小值,所以记录单调递减栈的每个点的出栈位置和入栈位置,分成两段,贡献做积*2
- 开ll,推式子
int main()
{
scanf("%s",str+1);
len=strlen(str+1);
num=128;
SA();
get_height();
int top;
st[top=1]=1;
rep(i,2,len)
{
while(top && height[st[top]]>height[i]) r[st[top--]]=i;
l[i]=st[top];
st[++top]=i;
}
while(top)r[st[top--]]=len+1;
ll ans= (ll)(len-1)*(len+1)*len/2;
for (int i=2;i<=len;i++)ans -=2ll*(r[i]-i)*(i-l[i])*height[i];
printf("%lld\n", ans);
}