后缀数组
后缀数组 (SA) 是一种重要的数据结构,通常使用倍增或者DC3算法实现,这超出了我们的讨论范围。
在本题中,我们希望使用快排、Hash与二分实现一个简单的O(nlog2n)的后缀数组求法。
详细地说,给定一个长度为 n 的字符串S(下标 0~n-1),我们可以用整数 k(0≤k<n) 表示字符串S的后缀 S(k~n-1)。
把字符串S的所有后缀按照字典序排列,排名为 i 的后缀记为 SA[i]。
额外地,我们考虑排名为 i 的后缀与排名为 i-1 的后缀,把二者的最长公共前缀的长度记为 Height[i]。
我们的任务就是求出SA与Height这两个数组。
输入格式
输入一个字符串,其长度不超过30万。
字符串由小写字母构成。
输出格式
第一行为数组SA,相邻两个整数用1个空格隔开。
第二行为数组Height,相邻两个整数用1个空格隔开,我们规定Height[1]=0。
输入样例:
ponoiiipoi
输出样例:
9 4 5 6 2 8 3 1 7 0
0 1 2 1 0 0 2 1 0 2
说实话看到这道题的时候真的是一脸懵,这东西咋用hash,二分,快排。
后来找了些博客看,终于理解其中的思路了,
一、先记录整条的hash值。
二、用一个sort函数,自定义cmp。
三、通过二分确定两个后缀字符之间的前缀相同字母。
这道题写这篇题解的时候又重新去写了一遍,感觉真的难。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef unsigned long long ull;
const int base = 131;
const int N = 3e5 + 10;
char str[N];
ull h[N], p[N];
int sa[N], n;
ull gethash(int l, int r) {//得到某一段的hash值
return h[r] - h[l - 1] * p[r - l + 1];
}
int sumsub(int a, int b) {
int l = 0, r = min(n - a + 1, n - b + 1);//取最小的。
while(l < r) {//二分。
int mid = (l + r + 1) >> 1;
if(gethash(a, a + mid - 1) != gethash(b, b + mid - 1)) r = mid - 1;
else l = mid;
}
return r;
}
bool cmp(int a, int b) {
int l = sumsub(a, b);//两个的相同前缀长度。
int x = a + l > n ? - 1e9 : str[a + l];如果有一个单词都是前缀,防止发生数组越界。
int y = b + l > n ? - 1e9 : str[b + l];
return x < y;
}
int main() {
scanf("%s", str + 1);//从第一个字符开始可以避免hash的边界问题。
n = strlen(str + 1);
p[0] = 1;
for(int i = 1; i <= n; i++) {
h[i] = h[i - 1] * base + str[i] - 'a' + 1;
p[i] = p[i - 1] * base;
sa[i] = i;
}
sort(sa + 1, sa + n + 1, cmp);//对下标进行排序。
for(int i = 1; i <= n; i++) printf("%d%c", sa[i] - 1, i == n ? '\n' : ' ');
printf("0 ");
for(int i = 2; i <= n; i++) printf("%d%c", sumsub(sa[i], sa[i - 1]), i == n ? '\n' : ' ');
return 0;
}
这个算法耗时还是非常长的,并不是真正的能用的算法,但是这个写法的综合力度还是比较高的,思想还是可以借鉴的。
来源:CSDN
作者:life丶happy
链接:https://blog.csdn.net/weixin_45483201/article/details/104739042