You are on page 1of 5

后缀数组

后缀 表示从某个位置 开始到整个串末尾结束

表示排名 的后缀为 , 表示后缀 的排名为 .

数组: ,其中 为最⻓公共前缀,即 表示第 名的


后缀与它前⼀名的后缀的最⻓公共前缀。

后缀数组的实现

倍增算法
倍增算法的思路为:⽤倍增的⽅法对每个字符开始的⻓度为 的⼦字符串进⾏排序,求出排名,即
值。 从0开始,每次加⼀,当 ⼤于 以后,每个字符开始的⻓度为 就是所有的后缀。每⼀次
排序都利⽤上次⻓度为 的字符串的 值

代码

1 const int maxn = 1e5 + 5;


2 const ll mod = 1e9 + 7;
3 /*
4 sa[i] : 排名为i的后缀起始下标
5 rk[i] : 起始下标为i的后缀的排名
6 height[i] : suffix[sa[i]]和suffix[sa[i - 1]]的最⻓公共前缀,即为排名相邻的两个后
缀的最⻓公共前缀
7 */
8 /*
9 *倍增算法nlogn
10 *将待排序数组放在0~n-1中,在最后补⼀个0
11 *build(,n+1,);//注意是n+1
12 *getHeight(,n);
13 *例如:
14 *n = 8;
15 *num[] = { 1, 1, 2, 1, 1, 1, 1, 2, $ };注意num最后⼀位为0,其他⼤于0
16 *Rank[] = { 4, 6, 8, 1, 2, 3, 5, 7, 0 };Rank[0~n-1]为有效值,Rank[n]必
定为0⽆效值
17 *sa[] = { 8, 3, 4, 5, 0, 6, 1, 7, 2 };sa[1~n]为有效值,sa[0]必定为n是
⽆效值
18 *height[]= { 0, 0, 3, 2, 3, 1, 2, 0, 1 };height[2~n]为有效值
19 */
20
21 char str[maxn];
22 int s[maxn];
23 int t1[maxn], t2[maxn], c[maxn], sa[maxn], rk[maxn], height[maxn];
24
25 void init() {
26 memset(t1, 0, sizeof(t1));
27 memset(t2, 0, sizeof(t2));
28 memset(c, 0, sizeof(c));
29 memset(sa, 0, sizeof(sa));
30 memset(rk, 0, sizeof(rk));
31 memset(height, 0, sizeof(height));
32 }
33 void get_SA(int s[], int n, int m) {
34 int *x = t1, *y = t2;
35 for (int i = 0; i < m; ++i) c[i] = 0;
36 for (int i = 0; i < n; ++i) c[x[i] = s[i]]++;
37 for (int i = 1; i < m; ++i) c[i] += c[i - 1];
38 for (int i = n - 1; i >= 0; --i) sa[--c[x[i]]] = i;
39 for (int k = 1; k <= n; k <<= 1) {
40 int p = 0;
41 for (int i = n - k; i < n; ++i) y[p++] = i;
42 for (int i = 0; i < n; ++i) if (sa[i] >= k) y[p++] = sa[i] - k;
43 for (int i = 0; i < m; ++i) c[i] = 0;
44 for (int i = 0; i < n; ++i) c[x[y[i]]]++;
45 for (int i = 0; i < m; ++i) c[i] += c[i - 1];
46 for (int i = n - 1; i >= 0; --i) sa[--c[x[y[i]]]] = y[i];
47 swap(x, y);
48 p = 1;
49 x[sa[0]] = 0;
50 for (int i = 1; i < n; ++i)
51 x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] ==
y[sa[i] + k] ? p - 1 : p++;
52 if (p >= n) break;
53 m = p;
54 }
55 }
56 void get_height(int s[], int n) {
57 int k = 0;
58 for (int i = 0; i <= n; ++i) rk[sa[i]] = i;
59 for (int i = 0; i < n; ++i) {
60 if (k) k--;
61 int j = sa[rk[i] - 1];
62 while (s[i + k] == s[j + k]) k++;
63 height[rk[i]] = k;
64 }
65 }
66 int main()
67 {
68 scanf("%s", str);
69 int n = strlen(str);
70 //将待排序数组放在0~n-1中
71 for (int i = 0; i < n; ++i) s[i] = str[i];
72 s[n] = 0; //补⻬0
73 init();
74 get_SA(s, n + 1, 128); //数组⻓度+1
75 get_height(s, n);
76 return 0;
77 }
78

应⽤

两⼦串的最⻓公共前缀

求两⼦串最⻓公共前缀就转换为了RMQ问题

单个字符串的相关问题
这类问题的常⻅做法就是先求后缀数组和 数组,然后利⽤ 数组进⾏求解

重复⼦串

重复⼦串:字符串R在字符串L中⾄少出现两次,则称R为L的重复⼦串

可重叠最⻓重复⼦串
等价于求 数组的最⼤值,因为任意两个后缀的最⻓公共前缀就是 数组⾥的某⼀段的最⼩

不可重叠最⻓重复⼦串

⼆分答案,判断是否存在两个⻓度为k的⼦串是相同的,且不重叠。

将排序后的后缀分成若⼲组,其中每组的后缀之间的 值都不⼩于k,再判断每组中每个后缀的
值的最⼤值和最⼩值的是否不⼩于k。

可重叠的k次的最⻓重叠⼦串

给定⼀个字符串,求⾄少出现k次的最⻓重复⼦串,这k个⼦串可以重叠。

先⼆分答案,将后缀分为若⼲组,判断存在⼀个组内存在后缀个数不少于k。

⼦串的个数
不相同的⼦串的个数:给定⼀个字符串,求不相同的⼦串的个数。

对每个后缀 ,将产⽣ 的新的前缀,但其中有 个的前缀是相同的,


因此每个后缀 的贡献为 。

回⽂⼦串
最⻓回⽂⼦串

计算以每个字符为中⼼的最⻓回⽂串,转化为求⼀个后缀和⼀个反过来的后缀的最⻓公共前缀,将整个
字符串逆反拼接在原字符串后⾯,中间加上特殊字符,再求出新字符串的某两个后缀的最⻓公共前缀。

连续重复⼦串
连续重复串:字符串L是由字符串S重复R次得到

最⻓重复⼦串

枚举字符串S的⻓度k,判断字符串L的⻓度是否能整除k并且判断 与 的lcp是
否为n-k。

重复次数最多的连续⼦串

两个字符串的相关问题
这类问题的常⻅做法:先连接两个字符串,然后求后缀数组和 数组,再利⽤ 数组进⾏求
解。

公共⼦串

公共⼦串指字符串L同时出现在字符串A和字符串B中

最⻓公共⼦串
将两个字符串拼接在⼀起,中间加上没有出现的特殊字符,求出满⾜ 和
不是同⼀个字符串中的两个后缀时, 的最⼤值

⼦串的个数

给定两个字符串A和B,求⻓度不少于k的公共⼦串的个数。

基本思路是计算A的所有后缀和B的所有后缀之间的最⻓公共前缀的⻓度,把最⻓公共前缀⻓度不少于k
的部分全部加起来。对 分组后,没遇到⼀个B的后缀就统计与前⾯的A的后缀能产⽣多少个⻓度
不少于k的公共⼦串。

You might also like