首先,我们来证明将字符串恰好分割成 k 个部分总是最优的。假设我们将其分割成 x > k 个部分;如果我们合并两个相邻的部分,可以证明按排序顺序排列的第 k 个部分的长度不会减少。假设我们把它切成几部分。各个部分;如果我们合并两个相邻的部分,可以证明:按排序顺序排列的第 1 部分不会减少。
证明:某个多重集的第 k 个最小元素可以定义为满足以下条件的最小值:该多重集包含至少 k 个不大于 a 的元素。假设我们合并字符串的两个部分:和;那么合并后的部分构成的多重集中会移除元素和,并插入一个新元素。对于任意字符串 a,不大于 a 的多重集中的元素个数不可能增加;因此,满足该多重集元素个数至少为 k 的最小值 a 不可能减少。所以,我们可以合并任意两个相邻的部分,结果都不会变差。某个多重集的第个最小元素可以定义为最小值使得多重集至少包含元素不大于假设我们将字符串的两个部分合并,和然后是元素和从结果部件的多重集中移除,并添加一个新元素。插入。对于每个字符串多重集中的元素个数不大于不能增加;因此,最小值其中此类元素的数量至少为不能减少。因此,我们可以合并任意两个相邻的部分,结果不会变差。
所以现在我们总是把字符串切成正好部分。当时,这意味着原始字符串本身就是答案(如果它的长度至少为);当时,我们需要考虑几个可能的答案,并从中选择最佳答案。部分。当这意味着原始字符串本身就是答案(如果它的长度至少为 1)。); 什么时候我们需要考虑几个可能的答案,并从中选择最佳答案。
假设我们要检查子串 是否可能是答案。那么必须满足以下条件:这可能是答案。那么必须满足以下条件:
- 位置 之前的前缀可以切成长度至少为 的字符串;可以剪成至少一定长度的条状;
- 位置 之后的后缀可以截断为长度至少为 的字符串;可以剪成至少一定长度的条状;
- 前缀和后缀可以被截断,使得它们组合在一起正好形成 个字符串,并且所有这些字符串在字典序上都不大于 。字符串,并且所有这些字符串的字典序都不大于。
第一和第二个条件很容易检查——如果前缀或后缀包含的字符数超过 且少于 ,则它不能被分割成长度至少为 的部分。然而,第三个条件的检查就困难得多。我们不直接检查它,而是利用我们感兴趣的最大合适字符串 的特性。这使得我们可以简化第三个条件,去掉剩余部分在字典序上不能大于 的要求——如果存在一个字典序大于 的字符串,那么我们也将其视为候选字符串,这意味着当前的字符串 无论如何都不可能是答案。且少于如果是字符,那么它至少不能被切割成若干长度的部分。然而,第三个条件更难验证。我们不直接验证它,而是利用我们感兴趣的是最大合适字符串这一事实。这样,我们就可以简化第三个条件,去掉剩余部分在字典序上不能大于某个特定值的要求。— 如果在这些部分中存在一个字符串,其字典序大于那么,我们也会将其视为候选字符串,这意味着当前字符串无论如何,这都不可能是答案。
这样,我们可以将条件重新表述如下:
- 位置 之前的前缀可以切成长度至少为 的字符串;可以剪成至少一定长度的条状;
- 位置 之后的后缀可以截断为长度至少为 的字符串;可以剪成至少一定长度的条状;
- 前缀和后缀可以被截断,使得它们组合在一起正好形成长度至少为 的 个字符串。长度至少为。
对于单个子串,这些条件可以在 内检查,这给我们提供了 个候选对象。这使我们有候选人。
利用从位置到位置的子串在字典序上总是大于从位置到位置的子串这一事实,可以将候选子串的数量减少到。因此,我们可以遍历左边界,并为每个左边界找到满足所有条件的最大右边界。这可以通过一个相当简单的计算来实现——我们需要确定前缀可以得到多少个子串,这将表明最小后缀长度是多少。利用位置子字符串这一事实定位始终在字典序上大于位置 的子字符串。定位因此,我们可以遍历左边界。并找到它们各自的最大右边界满足所有条件。这可以通过一个相当简单的计算来实现——我们需要确定前缀可以分成多少个部分,这将表明后缀的最小长度必须是多少。
现在我们有 O(n) 个字符串,每个字符串都是原字符串的子串,我们需要从中选择字典序最大的那个。遗憾的是,问题中的约束条件过于严格,无法方便地使用后缀结构(至少我们无法让后缀数组足够快速地工作)。我们提出两种解决方案:多个字符串,每个字符串都是原始字符串的子字符串。其中我们需要选择字典序最大的那个。遗憾的是,问题中的约束条件过于严格,无法方便地使用后缀结构(至少我们无法让后缀数组运行得足够快)。我们提出两种解决方案:
方法 1 — 哈希值 (FelixArg)
为字符串的所有前缀构建多项式哈希,并预先计算哈希基的所有幂(以及可能的模逆——尽管这不是必需的)。这使我们能够在中检查两个子串是否相等。。
但仅仅比较相等性是不够的;我们需要确定两个子串哪个更大,哪个更小。我们可以使用二分查找法,在 O(log n) 的时间复杂度内计算出两个子串的最长公共前缀。最长公共前缀之后的下一个字符就告诉我们哪个子串更大。一旦我们能够在 O(log n) 的时间复杂度内比较两个子串,我们就可以轻松地在 O(n log n) 的时间复杂度内找到其中的最大值。使用二分查找法比较子串的长度。最长公共前缀之后的下一个字符可以告诉我们哪个子串更长。一旦我们能够比较两个子串的长度,我们可以很容易地找到其中的最大值。。
为避免碰撞问题,我们建议使用双基/模哈希。
方法 2 — z 函数 (BledDest)
取候选答案 ,构建字符串 的 z 函数,其中 为分隔符。z 函数将显示 与其他每个候选答案的最长公共前缀的长度。这使我们能够将 与其他候选答案进行比较,从而将它们分成三组:并为该字符串构建 z 函数, 在哪里是某种分隔符。z 函数将显示最长公共前缀的长度以及其他每位候选人的答案。这使我们能够进行比较。与其他候选人一起,因此将他们分成三组:
- 小于 ;;
- 等于 ;;
- 大于 。。
如果第三个集合为空,那么就是问题的答案,因为它是最大值。否则,我们只对第三个集合感兴趣,可以继续分析它,而无需分析所有候选集合。由于它是最大值,因此是问题的答案。否则,我们只对第三组感兴趣,可以继续分析它,而无需分析所有候选集。
如果我们随机选择候选答案,平均需要处理 个候选答案(这可以用与随机快速排序的递归深度或随机笛卡尔树的深度大致相同的方式证明)。每个候选答案的检查时间为 ,因此总复杂度为 。候选答案的平均值为(这可以用与随机快速排序的递归深度或随机笛卡尔树的深度大致相同的方式证明)。每个候选答案都会被检查。,总复杂度为。