刷蓝桥杯题目的时候碰到了后缀数组,就去学习了一下,虽然可能因为python问题还是没ac
题目传送门[蓝桥杯 2014 国 AC] 重复模式
后缀数组
后缀数组(SA) 是强大的处理字符串的算法,看网上都是c++的代码,这里给出一手python的代码
前置定义:
字串: 在字符串s中,任取i<=j,则在s中截取从i到j的这一段就叫做s的字串
后缀: 就是从字符串s的某个位置i到字符串末尾的字串,我们定义以s的第i个字符为第一个元素的后缀为suff(i)
后缀数组: 把s的每个后缀按照字典序排序,
后缀数组SA[i] 表示 排名为i的后缀的起始下标位置,
而它的映射数组rank[i] 表示 起始位置下标为i的后缀的排名
简单来说,SA表示排名为i的起始下标,rank表示起始下标为i的排名是几
后缀数组的具体实现
在暴力的情况下,快排 O ( n l o g n ) O(nlogn) O(nlogn)排序每个后缀,但是这是字符串,比较任意两个后缀的时间 O ( n ) O(n) O(n),合起来接近 O ( n 2 l o g n ) O(n^2logn) O(n2logn)的复杂度,我们采取2方面的优化
倍增
对于读入的字符串根据单个字符排序,相同的字符拥有同样的序号,这里称作关键字
接下来把相邻的关键字合并到一起,即每个字符以自己的关键字为第一关键字,以下一个字符的关键字为第二关键字,没有第二关键字的补零,按照新合并的关键字排完序后再次标号
然后再以第i个字符自己的关键字为第一关键字,第i+2个字符的关键字为第二关键字,重复上一步,再以第i+4为第二关键字倍增 … \dots …直至排序标号后所有的关键字都不一样
显然这样排序的速度稳定在
O
(
l
o
g
n
)
O(logn)
O(logn)
基数排序
如果用快排的化,复杂度是
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)
观察排序过程,每次都是排序两位数,所以基数排序能优化到
O
(
n
)
O(n)
O(n)
显然后缀数组这东西不可能让你排个序就完事了
构造最长公共前缀——Height
定义:
Height[i]: 表示suff[SA[i]]和suff[SA[i - 1]]的最大公共前缀,也就是排序后相邻的两个后缀的最长公共前缀
而两个排名不相邻的最长公共前缀为排名在他们之间的Height的最小值
一般用h[i]表示Height[rank[i]]
利用后缀数组高效地得到Height
先给出一个结论h[i]
≥
\ge
≥ h[i - 1] - 1$$Height[rank[i]]
≥
\ge
≥ Height[rank[i - 1]]
这个性质很简单,大家花点时间自己想一下
根据这个性质,我们遍历一遍SA就可以得到Height了
代码
有点绕,慢慢理解下
def get_SA(s, n, m):
MAXN = 500005
rank = [0] * MAXN
c = [0] * MAXN
sa = [0] * MAXN
y = [0] * MAXN
Height = [0] * MAXN
for i in range(1, n + 1):
rank[i] = ord(s[i - 1])
c[rank[i]] += 1
for i in range(1, m):
c[i] += c[i - 1]
for i in range(n, 0, -1):
sa[c[rank[i]]] = i
c[rank[i]] -= 1
k = 1
while(k <= n):
num = 0
for i in range(n - k + 1, n + 1):
num += 1
y[num] = i
for i in range(1, n + 1):
if(sa[i] > k):
num += 1
y[num] = sa[i] - k
for i in range(1, m + 1):
c[i] = 0
for i in range(1, n + 1):
c[rank[i]] += 1
for i in range(1, m + 1):
c[i] += c[i - 1]
for i in range(n, 0, -1):
sa[c[rank[y[i]]]] = y[i]
c[rank[y[i]]] -= 1
y[i] = 0
rank, y = y, rank
rank[sa[1]] = 1
num = 1
for i in range(2, n + 1):
if(y[sa[i]] == y[sa[i - 1]] and y[sa[i] + k] == y[sa[i - 1] + k]):
rank[sa[i]] = num
else:
num += 1
rank[sa[i]] = num
if(num == n):
break
m = num
k *= 2
for i in range(1, n + 1):
k = max(k - 1, 0) if k else k
j = sa[rank[i] - 1]
while(i + k <= n and j + k <= n and s[i + k - 1] == s[j + k - 1]):
k += 1
Height[rank[i]] = k
ans = 1
for i in range(2, n + 1):
ans = max(Height[i], ans)
print(ans)
if __name__ == '__main__':
s = input()
n = len(s)
get_SA(s, n, 256)