求排名为K的子串

从前有这样一种问题,给我们一个字符串,让我们求其所有不相同的子串中按字典序排名为K的子串

在讨论这个问题的解法之前,不妨先看如何这样一个问题:如何求一个字符串的所有不相同的子串的个数?

很容易理解,一个字符串的任意一个子串都是母串的某个后缀的某个前缀,那么求一个字符串所有不同子串的个数就相当于求其所有后缀的不同前缀的个数,至此我们想到可以对一个字符串的所有后缀按字典序排序,这样拥有相同前缀的后缀就放到了相邻的位置,再计算每个后缀对答案的贡献即可。

先拿"HOMURA"这个字符串举个例子。

我们先对其所有后缀排序,得到如下结果:

排名 后缀
1 A
2 HOMURA
3 MURA
4 OMURA
5 RA
6 URA

每个后缀的答案的贡献即为其前缀的数量,也就是其长度,那么"HOMURA"这个字符串的所有不同子串的数量即为N=1+6+4+5+2+3=21

再看下一个例子"ABABA"

排名 后缀 LCP
可以使用后缀数组来解决这个问题。先构建字符串 s 的后缀数组 sa,并预处理出它的 height 数组。height[i] 表示排名为 i 的后缀排名为 i-1 的后缀的最长公共前缀。 然后从后往前遍历 height 数组,找到第一个 height[i] 小于 height[i+1] 的位置 i。其实就是找到了子串 s[sa[i]...sa[i]+height[i]] 的结尾在所有子串中排在最后的那个。 C++代码如下: int sa[N], rk[N], height[N]; int tmp1[N], tmp2[N], c[N]; void build_sa(char *s, int n) { int m = 256; for (int i = 0; i < n; ++i) ++c[rk[i] = s[i]]; for (int i = 1; i < m; ++i) c[i] += c[i-1]; for (int i = n-1; i >= 0; --i) sa[--c[s[i]]] = i; for (int k = 1; k <= n; k <<= 1) { int num = 0; for (int i = n-k; i < n; ++i) tmp2[num++] = i; for (int i = 0; i < n; ++i) if (sa[i] >= k) tmp2[num++] = sa[i]-k; for (int i = 0; i < m; ++i) c[i] = 0; for (int i = 0; i < n; ++i) ++c[tmp1[i] = rk[i]]; for (int i = 1; i < m; ++i) c[i] += c[i-1]; for (int i = n-1; i >= 0; --i) sa[--c[tmp1[tmp2[i]]]] = tmp2[i]; swap(rk, tmp1); rk[sa[0]] = 0; num = 1; for (int i = 1; i < n; ++i) { if (tmp1[sa[i]] == tmp1[sa[i-1]] && tmp1[sa[i]+k] == tmp1[sa[i-1]+k]) rk[sa[i]] = num-1; else rk[sa[i]] = num++; } if (num >= n) break; m = num; } } void build_height(char *s, int n) { for (int i = 0; i < n; ++i) rk[sa[i]] = i; int k = 0; for (int i = 0; i < n; ++i) { if (rk[i] == 0) continue; if (k) --k; int j = sa[rk[i]-1]; while (s[i+k] == s[j+k]) ++k; height[rk[i]] = k; } } char s[N]; int main() { scanf("%s", s); int n = strlen(s); build_sa(s, n+1); build_height(s, n); int ans = 0; for (int i = 1; i <= n; ++i) { if (height[i] < height[i+1]) ans = i; } printf("%s\n", s+sa[ans]); return 0; }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值