Loading docs/string/sa.md +14 −22 Original line number Diff line number Diff line Loading @@ -6,13 +6,13 @@ ### 各种定义 `子串` :就是子串[捂脸] `子串` :就是子串。[捂脸] `后缀` :就是从 $i$ 这个位置开始到该字符串的末尾的一个子串 `后缀` :就是从 $i$ 这个位置开始到该字符串的末尾的一个子串。 `字符串大小比较` : $a$ 和 $b$ 这两个串,从头开始逐个字符按照 ASCII 码进行比较。(按照字典序比较) `字符串大小比较` : 把 $a$ 和 $b$ 这两个串按照字典序进行比较。 `后缀数组` : $sa[i]$ 代表该字符串的 $len$ 个后缀中,从 $sa[i]$ 开始的后缀排在为 $i$ 个。 $sa$ 数组记录的是“排第几的哪个后缀”。 `后缀数组` : $sa[i]$ 代表该字符串的 $len$ 个后缀中,从 $sa[i]$ 开始的后缀排在为 $i$ 个。 $sa$ 数组记录的是“排第几的是哪个后缀”。 `名次数组` : $rank[i]$ 代表从 $i$ 开始的后缀排名为 $rank[i]$ 。 $rank$ 数组记录的是“某个后缀排在第几个”。 Loading @@ -20,7 +20,7 @@ ### 最简单的暴力 把所有的后缀拆出来,然后 sort。由于直接比较长度为 n 的字符串的时间复杂度为 $O(n)$ ,所以整体时间复杂度为 $O(n^2 \log n)$ 把所有的后缀拆出来,然后 sort。由于直接比较长度为 $n$ 的字符串的时间复杂度为 $O(n)$ ,所以整体时间复杂度为 $O(n^2 \log n)$。 ```cpp int rank[123], sa[123]; Loading Loading @@ -53,15 +53,9 @@ int main() { ### 倍增法 这个就是一般人写后缀数组用的方法 这个就是一般写后缀数组用的方法,复杂度是 $O(n\log n)$,前提是你要先会**基数排序**。 复杂度是 $O(n\log n)$ 前提是你要先会**基数排序** 假设我们有这样一个字符串 `aabaaaab` 然后我们把所有的后缀列举出来: 假设我们有这样一个字符串 `aabaaaab` ,然后我们把所有的后缀列举出来:  Loading @@ -69,19 +63,17 @@ int main() {  接着我们以第二个字母为关键字,在首字母有序的基础上进行排序,这个时候,我们把首字母相同的后缀拿出来单看 接着我们以第二个字母为关键字,在首字母有序的基础上进行排序,这个时候,我们把首字母相同的后缀拿出来单看。 对于每一组首字母相同的后缀,首字母是对排序没有影响的,所以可以直接按照第二个字母进行基数排序,同样,对于首字母不同的后缀,由于按照首字母排序时,他们的相对大小已经确定,当按照第二个字母排序时,不会出现 `原来 a>b,现在 b>a` 的现象,所以我们可以看成一直在做区域内的排序,这之后变成这样: 对于每一组首字母相同的后缀,首字母是对排序没有影响的,所以可以直接按照第二个字母进行基数排序,同样,对于首字母不同的后缀,由于按照首字母排序时,他们的相对大小已经确定,当按照第二个字母排序时,不会出现 “原来 a>b,现在 b>a” 的现象,所以我们可以看成一直在做区域内的排序,这之后变成这样:  第三字母同理…… 这样子我们可以处理这个问题,可是复杂度还是没有到达一个我们可以接受的范围 所以我们引入**倍增** 这样子我们可以处理这个问题,可是复杂度还是没有到达一个我们可以接受的范围,所以我们引入**倍增**。 当我们按照每个后缀的前 $2^k$ 个字母进行完排序后,那么我们把后缀的前 $2^{k+1}$ 看做前后两个 $2^k$ , 这样我们就可以把这前后两个 $2^k$ 作为之前说的 `首字母` 和 `第二个字母` 了,然后进行上述过程,就可以在 $O(nlogn)$ 的复杂度内处理这个问题了 当我们按照每个后缀的前 $2^k$ 个字母进行完排序后,那么我们把后缀的前 $2^{k+1}$ 看做前后两个 $2^k$ , 这样我们就可以把这前后两个 $2^k$ 作为之前说的首字母和第二个字母了,然后进行上述过程,就可以在 $O(nlogn)$ 的复杂度内处理这个问题了。 ```cpp #include <bits/stdc++.h> Loading Loading @@ -132,10 +124,10 @@ int main() { } ``` 代码里 $x[i]$ 就是 $rank[i]$ 代码里 $x[i]$ 就是 $rank[i]$。 $y[i]$ :假设 $y[i]=a\ ,\ y[i+1]=b$ 那么在原串中 从 $a+2^k$ 开始的 $2^k$ 个字符组成的子串**小于等于**从 $b+2^k$ 开始的 $2^k$ 个字符组成的子串 $y[i]$:假设 $y[i]=a\ ,\ y[i+1]=b$ 那么在原串中从 $a+2^k$ 开始的 $2^k$ 个字符组成的子串**小于等于**从 $b+2^k$ 开始的 $2^k$ 个字符组成的子串。 最好理解这个代码时,每一步都结合这基数排序来考虑 最好理解这个代码时,每一步都结合这基数排序来考虑。 ### DC3 Loading
docs/string/sa.md +14 −22 Original line number Diff line number Diff line Loading @@ -6,13 +6,13 @@ ### 各种定义 `子串` :就是子串[捂脸] `子串` :就是子串。[捂脸] `后缀` :就是从 $i$ 这个位置开始到该字符串的末尾的一个子串 `后缀` :就是从 $i$ 这个位置开始到该字符串的末尾的一个子串。 `字符串大小比较` : $a$ 和 $b$ 这两个串,从头开始逐个字符按照 ASCII 码进行比较。(按照字典序比较) `字符串大小比较` : 把 $a$ 和 $b$ 这两个串按照字典序进行比较。 `后缀数组` : $sa[i]$ 代表该字符串的 $len$ 个后缀中,从 $sa[i]$ 开始的后缀排在为 $i$ 个。 $sa$ 数组记录的是“排第几的哪个后缀”。 `后缀数组` : $sa[i]$ 代表该字符串的 $len$ 个后缀中,从 $sa[i]$ 开始的后缀排在为 $i$ 个。 $sa$ 数组记录的是“排第几的是哪个后缀”。 `名次数组` : $rank[i]$ 代表从 $i$ 开始的后缀排名为 $rank[i]$ 。 $rank$ 数组记录的是“某个后缀排在第几个”。 Loading @@ -20,7 +20,7 @@ ### 最简单的暴力 把所有的后缀拆出来,然后 sort。由于直接比较长度为 n 的字符串的时间复杂度为 $O(n)$ ,所以整体时间复杂度为 $O(n^2 \log n)$ 把所有的后缀拆出来,然后 sort。由于直接比较长度为 $n$ 的字符串的时间复杂度为 $O(n)$ ,所以整体时间复杂度为 $O(n^2 \log n)$。 ```cpp int rank[123], sa[123]; Loading Loading @@ -53,15 +53,9 @@ int main() { ### 倍增法 这个就是一般人写后缀数组用的方法 这个就是一般写后缀数组用的方法,复杂度是 $O(n\log n)$,前提是你要先会**基数排序**。 复杂度是 $O(n\log n)$ 前提是你要先会**基数排序** 假设我们有这样一个字符串 `aabaaaab` 然后我们把所有的后缀列举出来: 假设我们有这样一个字符串 `aabaaaab` ,然后我们把所有的后缀列举出来:  Loading @@ -69,19 +63,17 @@ int main() {  接着我们以第二个字母为关键字,在首字母有序的基础上进行排序,这个时候,我们把首字母相同的后缀拿出来单看 接着我们以第二个字母为关键字,在首字母有序的基础上进行排序,这个时候,我们把首字母相同的后缀拿出来单看。 对于每一组首字母相同的后缀,首字母是对排序没有影响的,所以可以直接按照第二个字母进行基数排序,同样,对于首字母不同的后缀,由于按照首字母排序时,他们的相对大小已经确定,当按照第二个字母排序时,不会出现 `原来 a>b,现在 b>a` 的现象,所以我们可以看成一直在做区域内的排序,这之后变成这样: 对于每一组首字母相同的后缀,首字母是对排序没有影响的,所以可以直接按照第二个字母进行基数排序,同样,对于首字母不同的后缀,由于按照首字母排序时,他们的相对大小已经确定,当按照第二个字母排序时,不会出现 “原来 a>b,现在 b>a” 的现象,所以我们可以看成一直在做区域内的排序,这之后变成这样:  第三字母同理…… 这样子我们可以处理这个问题,可是复杂度还是没有到达一个我们可以接受的范围 所以我们引入**倍增** 这样子我们可以处理这个问题,可是复杂度还是没有到达一个我们可以接受的范围,所以我们引入**倍增**。 当我们按照每个后缀的前 $2^k$ 个字母进行完排序后,那么我们把后缀的前 $2^{k+1}$ 看做前后两个 $2^k$ , 这样我们就可以把这前后两个 $2^k$ 作为之前说的 `首字母` 和 `第二个字母` 了,然后进行上述过程,就可以在 $O(nlogn)$ 的复杂度内处理这个问题了 当我们按照每个后缀的前 $2^k$ 个字母进行完排序后,那么我们把后缀的前 $2^{k+1}$ 看做前后两个 $2^k$ , 这样我们就可以把这前后两个 $2^k$ 作为之前说的首字母和第二个字母了,然后进行上述过程,就可以在 $O(nlogn)$ 的复杂度内处理这个问题了。 ```cpp #include <bits/stdc++.h> Loading Loading @@ -132,10 +124,10 @@ int main() { } ``` 代码里 $x[i]$ 就是 $rank[i]$ 代码里 $x[i]$ 就是 $rank[i]$。 $y[i]$ :假设 $y[i]=a\ ,\ y[i+1]=b$ 那么在原串中 从 $a+2^k$ 开始的 $2^k$ 个字符组成的子串**小于等于**从 $b+2^k$ 开始的 $2^k$ 个字符组成的子串 $y[i]$:假设 $y[i]=a\ ,\ y[i+1]=b$ 那么在原串中从 $a+2^k$ 开始的 $2^k$ 个字符组成的子串**小于等于**从 $b+2^k$ 开始的 $2^k$ 个字符组成的子串。 最好理解这个代码时,每一步都结合这基数排序来考虑 最好理解这个代码时,每一步都结合这基数排序来考虑。 ### DC3