求正整数中第K个二制中不含11的数

本文介绍了一种算法,用于找出第K个不含连续11子串的二进制字符串。通过动态规划确定目标串长度,并递归生成具体字符串。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

描述很简单:把所有的正整数从小到大都转化成二进制串,求第K(K<900000000)个不含11子串的二制串。如:1,2,3,4,5的二进制串为1,10,11,100,101,故前四个满足条件的串分别为1,10,100,101。

首先最想知道的就是第K个满足上述条件的串有多少位?1位的串有1个,2位的串有1个,3位的中有2个……其实我们发现可以构造出来。对于长度为N的串,对于这些串如果尾数为0,那么在后面增加0,1都可以;如果尾数为1,那么就只能增加0。这样就包含了所有的满足条件的长度为N+1的串。下面给出简单的证明:

由于N+1长的串是满足条件的,必然所有的N+1长的串的前N个字符组成的子串必然要满足条件,故前N个字符的子串的形式必定有所有长为N的串。得到了所有长为N+1串的前N个字符的串,只能在后面增加0或1,对于串尾为0,增加0,1后满足条件,对于串尾为1,必然只能增加0。故得证。如下图所示:

 

这样通过动态规划就可以求得长度为N的串一共有多少个,于是也就求得第K个满足条件的串的长度为多少。

然而这样还不够,我们只是知道了这样的串长为多少,我们并不知道这样的串是什么。那么怎么样得生成我们需要的串呢?如需求第7个满足条件的串的是多少。首先可以得到长度为4(从上图可看出来)。比4小的串有多少个呢?1,10,100,101,又可以得到第7个满足条件的串必然在长度为4的串中排第三(1000,1001,1010)。那么长度为4排第三的串又是多少呢?0,1,10……这样往下我们发现,问题转化为更小的,第2个满足条件的串是多少……以些类推,最终就可以求得解。

<think>嗯,用户的问题是关于如何通过C++编写程序来解决给定n个长度为m的环形字符串之间的最长公共子串问题,并要最大化边权和。这个问题看起来有点复杂,我需要仔细分析一下。 首先,用户提到的环形字符串可能意味着每个字符串是首尾相连的,比如引用1和引用3中的例子。这样的话,子串可以从任意位置开始,并可以环绕到开头。例如,在引用3的示例2中,最长子字符串是首尾连接的。所以处理环形字符串时,可能需要将字符串复一次,比如将s变为s+s,然后处理长度超过m的子串,这样可以避免环形带来的复杂性。这种方法在解决环形组问题时常用,比如最大子组和的问题。 接下来,问题涉及多个字符串之间的最长公共子串。通常,寻找多个字符串的最长公共子串是一个难题,尤其是当n较大时。但是用户提到n个长度为m的环形字符串,所以可能需要考虑所有字符串的公共子串,并是环形的。此外,用户还提到要最大化边权和,这里的“边权”可能是指子串中每个字符的权重之和,或者可能与子串的位置有关。过用户没有给出边权的具体定义,可能需要进一步明确。假设边权是指子串本身的长度,那么最大化边权和可能等同于寻找最长的公共子串。但需要确认这一点。 然后,我需要参考用户提供的引用内容。引用1和引用3都涉及到环形字符串中寻找满足特定条件的最长子串。例如,引用3中的问题需要找到包含'l','o','x'字符出现偶次的最长子串,并处理环形的情况。这可能对处理当前问题的方法有启发,比如如何处理环形结构,可能采用将字符串扩展为两倍长度的方法。 引用2中的C++代码是关于最长不含重复字符的子串,使用了滑动窗口的方法。这可能对处理子串问题有帮助,但当前问题涉及多个字符串的公共子串,可能需要同的方法。 现在,问题需要解决的是n个环形字符串的最长公共子串,并最大化边权和。这里可能的思路是: 1. 环形处理:将每个字符串扩展为两倍长度,以处理环形的情况。这样,任何起始位置的子串都可以在扩展后的字符串中找到,只要长度超过原长度m。 2. 最长公共子串:对于多个字符串来说,寻找所有字符串的公共子串通常比较困难。常用的方法包括使用后缀自动机或者动态规划。但n较大的情况下,时间复杂度可能很高。 假设n较小,比如n=2,那么可以比较两个字符串的所有可能子串。但对于较大的n,这种方法可行。用户没有给出n的范围,但题目中提到每个字符串长度为m,可能m较大(比如500000),因此需要考虑高效的算法。 另一个可能的思路是,对于每个字符串,生成所有可能的子串(考虑环形),然后找到所有字符串共有的最长子串。但这种方法的时间和空间复杂度会非常高,尤其是当m很大时。 或者,可以考虑将每个环形字符串的所有可能旋转形式进行处理,然后找到所有字符串共有的某个子串。例如,对于每个字符串,生成其所有可能的旋转后的字符串,然后找出公共子串。然而,这样的处理对于长度为m的字符串来说,会产生m个同的旋转形式,当m很大时,这可行。 或者,可以考虑将每个环形字符串视为其所有可能起始位置的循环,然后在每个字符串中寻找最长的公共子串。例如,将每个字符串s复为s+s,然后在每个这样的扩展字符串中寻找长度为m以内的子串,作为可能的候选。 但如何高效地找到多个字符串的公共子串呢?一个可能的优化方法是,首先找到所有字符串的所有可能的子串,然后取交集,找出最长的那个。但这对于大m来说现实。 另一个思路是,使用后缀自动机来处理多个字符串的最长公共子串。例如,可以构建一个广义后缀自动机,然后找到在所有字符串中都出现的最长子串。但后缀自动机的实现可能较为复杂,尤其是在处理环形字符串的情况下。 此外,用户提到要最大化边权和,可能需要明确边权的计算方式。假设边权是指子串长度,那么最长公共子串的边权和就是其长度,因此问题简化为找到最长的公共子串。如果边权有其他定义,可能需要同的处理方式。但用户没有给出具体定义,这里可能需要假设边权即长度。 综上所述,可能的解决步骤是: 1. 处理环形字符串:将每个字符串s扩展为s+s,然后限子串长度超过m。 2. 对于所有n个扩展后的字符串,找到它们的最长公共子串子串长度超过m。 3. 返回该子串长度作为最大边权和。 接下来,如何实现多个字符串的最长公共子串?对于两个字符串的情况,可以使用动态规划或者后缀自动机。例如,动态规划方法的时间复杂度是O(m^2),这在m较大时可行。而使用后缀自动机可能更高效,但实现复杂。 对于多个字符串的情况,通常的方法是取第一个字符串的所有后缀,然后在其他字符串中检查是否存在这些后缀的前缀。记录所有字符串中都存在的最长前缀。这种方法的时间复杂度取决于字符串量和长度,但可能需要优化。 考虑到用户需要C++代码实现,可能要考虑如何高效处理这个问题。例如,对于n个字符串,可以构建一个后缀自动机,然后对于每个状态,检查是否在所有字符串中都有出现。 或者,可以采用分查找的方法,假设最长公共子串长度为L,检查是否存在长度为L的子串在所有字符串中都出现。对于每个可能的L(通过分搜索确定最大值),可以在每个字符串中预处理所有长度为L的子串的哈希值,然后检查是否存在一个哈希值在所有字符串中都存在。这种方法的时间复杂度为O(n*m*log m),可能适用于较大的m。 另外,环形字符串的处理可能需要特殊考虑。例如,对于每个字符串s,生成s+s,然后对于每个可能的起始位置i(0<=i<m),生成长度为L的子串,其中L的范围是1到m。然后检查这个子串是否在所有其他字符串的扩展字符串中出现过。 这可能非常耗时,尤其是当n和m较大时。例如,当m=5e5,n=10时,这样的方法可能需要O(n*m^2)的时间,这显然可行。 因此,需要寻找更高效的算法。可能的一个优化是,对于每个字符串,生成其所有可能的循环子串的滚动哈希,并将这些哈希存储在哈希表中。然后,寻找在所有字符串的哈希表中都存在的哈希值,并记录最大的长度。这种方法的时间复杂度可能为O(n*m),但需要具体实现。 例如,使用Rabin-Karp算法为每个字符串预处理所有可能的子串的哈希值,并存储在一个哈希表中。然后,对于每个可能的长度L,从最长到最短,检查是否存在一个哈希值在每个字符串的哈希表中都存在。如果存在,则返回L作为最大边权和。 然而,这样的预处理对于每个字符串来说,需要O(m^2)的时间,当m=5e5时,这显然可能。因此,这样的方法可行。 因此,可能需要寻找另一种方法。或许,可以将环形字符串的问题转换为线性字符串的问题,通过将每个字符串扩展为两倍长度,然后在每个扩展后的字符串中寻找长度为L的子串,并子串出现在所有其他字符串的扩展字符串中。这样,可以将问题转换为在n个扩展后的字符串中寻找最长公共子串长度超过m。 对于n个字符串的最长公共子串问题,一种可能的解决方案是使用后缀自动机。首先,对第一个字符串构建后缀自动机,然后对于每个后续字符串,记录每个状态在该字符串中的出现情况。最终,找到在所有字符串中都出现的最长子串。这种方法的时间复杂度为O(n*m),可能适用于较大的m值。 过,这样的实现比较复杂,特别是在处理多个字符串的时候。此外,还需要处理环形字符串的情况,即子串可以跨越原始字符串的末尾和开头。 例如,对于每个字符串s,构建其扩展后的字符串s+s,并构建后缀自动机。然后,寻找在所有后缀自动机中都存在的最长子串长度超过m。 这可能需要将每个字符串的扩展版本处理后,逐步与其他字符串的自动机进行交集操作,但这样的操作可能非常复杂。 另一种思路是,将每个字符串的所有旋转视为可能的候选,然后寻找所有字符串共有的子串。例如,对于每个字符串,生成其所有可能的旋转(即循环移位后的字符串),然后找到所有旋转中的公共子串。但这种方法的时间复杂度太高,当m很大时可行。 回到用户的问题,可能需要进一步澄清。例如,“边权和”具体指什么?是否指子串长度?或者每个字符有权值,边权和是字符权值的总和?如果边权是子串长度,那么最大化边权和等同于寻找最长公共子串。如果边权是其他定义,可能需要同的处理方式。 假设边权和即子串长度,那么问题转化为寻找n个环形字符串的最长公共子串长度。 接下来,如何处理环形字符串?例如,对于每个字符串s,将其扩展为s+s,这样任何起始位置的长度为L≤m的子串都可以在扩展后的字符串中找到。然后,问题转化为在n个扩展后的字符串中寻找最长的公共子串长度超过m。 但是,直接处理n个扩展后的字符串的最长公共子串仍然困难。可能需要采用分法结合哈希的方法: 1. 使用分法确定可能的长度L,从最大的可能值m开始,逐步减小。 2. 对于每个L,检查是否存在一个长度为L的子串,在所有n个扩展后的字符串中都存在。 3. 一旦找到这样的L,即为答案。 检查是否存在这样的L可以使用滚动哈希(Rabin-Karp算法)来优化。 具体步骤: 对于每个字符串s_i,预处理其扩展后的字符串s_i+s_i的所有长度为L的子串的哈希值,并将这些哈希值存入一个哈希集合中。然后,取所有字符串的哈希集合的交集,如果交集非空,则存在这样的L。 但由于L可能很大,这样的预处理在每次分时都需要处理所有字符串,时间复杂度可能很高。例如,对于每个分步骤中的L,需要O(n*m)的时间预处理哈希,总时间复杂度为O(n*m*log m),这在m=5e5时可能无法接受。 因此,需要更高效的哈希预处理方法。例如,预先为每个字符串的所有可能L计算哈希,这在存储上是可行的。因此,可能需要另一种方法。 另一种思路是,对于每个字符串s,计算其所有可能长度的所有子串的哈希,但这样存储量太大。因此,可能无法实现。 或许,可以将问题限为寻找最长公共子串长度,而管具体位置。这时,可以采用后缀自动机的方法。 例如,首先构建第一个字符串的后缀自动机,然后对于每个后续字符串,更新自动机以仅保留在该字符串中出现的子串。最终,自动机中的最长状态即为所有字符串的公共最长子串长度过,这样的方法需要逐个处理每个字符串,并更新自动机,可能比较高效。然而,处理环形字符串的后缀自动机需要考虑扩展后的字符串s+s,并在其中寻找长度超过m的子串。 具体步骤: 1. 对于每个字符串s_i,构建其扩展后的字符串s_i+s_i的后缀自动机,但限子串的最大长度为m。或者,在构建后缀自动机时,记录每个状态的最大可能长度超过m。 2. 然后,找到所有后缀自动机的交集,即所有公共子串。其中最长的即为所过,如何多个后缀自动机的交集可能比较复杂。通常,两个自动机的交集已经比较困难,而n个自动机的交集更复杂。因此,这种方法可能可行。 或许,可以采用另一种方法:找到所有字符串的所有公共子串,并记录最长的那个。但如何高效实现? 这里可能需要参考一些现有的算法或论文,寻找多个字符串的最长公共子串的高效算法。例如,对于多个字符串的最长公共子串问题,已知的算法时间复杂度为O(n*m),其中n是字符串量,m是字符串长度。例如,使用广义后缀树或后缀自动机。 假设我们使用广义后缀自动机,构建包含所有字符串的后缀自动机,然后检查每个状态是否在所有字符串中都存在。如果是,则记录其长度。最后取最大的长度作为答案。 构建广义后缀自动机的过程是逐个插入字符串,并在每次插入后,记录每个状态的出现情况。例如,每个状态维护一个位掩码,表示哪些字符串包含该状态对应的子串。然后,最终检查位掩码是否为全1(即所有n个字符串都包含子串),并记录最大长度。 这种方法的时间复杂度为O(n*m),可能适用于较大的m值。 但需要处理的是环形字符串的情况。由于每个字符串是环形的,所以它们的子串可以是任何循环移位后的子串。因此,可能需要将每个字符串处理为其扩展后的s+s形式,并在其中寻找长度超过m的子串。 因此,具体步骤可能如下: 1. 对每个字符串s_i,构造其扩展后的字符串t_i = s_i + s_i。 2. 构建广义后缀自动机,包含所有t_i的所有子串,但限子串长度超过m。 3. 对于自动机中的每个状态,检查该状态对应的子串是否在所有t_i中出现过,并长度超过m。 4. 找出满足条件的最长子串长度,即为所的最大边权和。 但如何确保子串在t_i中出现时,其长度超过m?可能需要在对每个字符串构建后缀自动机时,记录每个状态的最大可能长度超过m。或者,在后续处理时,将超过m长度子串排除。 或者,在构建后缀自动机时,对于每个字符串t_i,处理时当子串长度超过m时,再继续扩展。这可能比较复杂。 或者,在构建广义后缀自动机时,每个状态记录该子串在哪些字符串中出现,并记录该子串在各个字符串中的最大可能长度超过m)。 这可能需要更复杂的状态管理,但可能可行。 例如,在插入字符串t_i时,当处理到某个位置,子串长度可能超过m,此时可以限超过m。但具体实现可能涉及在插入过程中跟踪当前子串长度,并确保超过m。 或者,将每个t_i的处理限为前2m个字符,并在插入到后缀自动机时,只考虑长度超过m的子串。例如,对于每个位置j in 0..2m-1,在插入字符t_i[j]时,维护当前子串的起始位置,使得子串长度<=m。这可能比较复杂,但或许可以通过滑动窗口的方式处理。 例如,使用滑动窗口的方法,维护窗口的左右指针,使得窗口长度超过m。这样,对于每个t_i,插入其所有可能的长度为1到m的子串到后缀自动机中。然后,构建广义后缀自动机,并检查哪些子串在所有字符串中出现过。 但是,这样的方法会导致插入大量子串,时间复杂度可能变为O(n*m^2),这在m=5e5时显然可行。 因此,必须寻找更高效的算法。 或许,可以换一个思路:每个环形字符串的最长子串可以出现在任何起始位置,并可以环绕。因此,对于每个字符串,其所有可能的循环子串可以等价于其线性扩展后的字符串中长度为L≤m的子串。因此,问题转化为在n个扩展后的字符串中寻找最长的公共子串长度超过m。 此时,问题等同于在n个字符串的扩展后的版本中寻找最长的公共子串子串长度超过m。 对于这个问题,可以使用滑动窗口和哈希的方法。例如,对于每个可能的L(从m到1递减),检查是否存在一个长度为L的子串,在所有扩展后的字符串中出现。一旦找到,则返回L。 为了高效检查,可以使用滚动哈希预处理每个扩展后的字符串的所有长度为L的子串的哈希值,然后将这些哈希值存入哈希集合,并计算它们的交集。如果交集非空,则存在这样的L。 具体步骤: 1. 预处理每个字符串s_i,生成t_i = s_i + s_i。 2. 对于当前的L(使用分法从高到低),遍历每个t_i,计算所有长度为L的子串的哈希值,并存入一个哈希集合中。同时,记录这些子串在原始s_i中的位置是否允许长度L超过m。例如,对于t_i中从位置j开始的子串,其长度为L,那么必须满足j + L <= 2m,并 L <=m。或者,更准确地说,当子串跨越s_i的末尾和开头时,其实际长度超过m。例如,在t_i中,子串可以从j开始,长度为L,其中j的范围是0到m-1,这样L最大可以到m。 或者,可能需要特别处理,因为当L <=m时,任何从j开始的子串(j在0到m-1)的长度L的子串,在t_i中对应的环形子串是有效的。例如,当j >=m时,子串可能重复,但可能已经被覆盖。 这可能比较复杂,需要更仔细的处理。 例如,对于每个扩展后的t_i = s_i + s_i,我们需要找到所有长度为L的子串,其中L <=m,并这些子串对应于s_i的环形子串。也就是说,子串在t_i中的起始位置可以是0到m-1,并长度为L,或者起始位置超过m,但取模后的位置在0到m-1范围内。 但为了简化,可以认为在t_i中,所有长度为L的子串(L <=m)的起始位置j的范围是0 <= j < 2m - L +1。但其中,当j + L >m时,子串可能跨越原始s_i的末尾和开头。例如,当j= m - L +1时,子串的末尾在m的位置,之后的部分来自s_i的开头。 但为了确保每个子串是原环形字符串的有效子串(即长度为L),可以认为在t_i中,任何长度为L的子串,其中L <=m,都是有效的环形子串。因此,在预处理时,对于每个t_i,生成所有长度为L的子串的哈希,并记录它们的哈希值。 然后,在所有n个字符串的哈希集合中,寻找是否存在至少一个哈希值,出现在所有n个集合中。如果存在,则L是可行的。 这种方法的关键在于,如何高效地生成每个字符串的哈希集合,并进行交集检查。由于L可能很大,比如m=5e5,所以需要高效的滚动哈希方法。 滚动哈希(Rabin-Karp)算法可以在O(1)时间计算每个子串的哈希,前提是预处理好前缀哈希和幂次组。例如,对于字符串t,预处理其前缀哈希组hash和幂组pow,那么子串t[j..j+L-1]的哈希值为 (hash[j+L] - hash[j] * pow[L]) mod mod_value。 这样,预处理时间为O(m),每个L的处理时间为O(m),因为需要遍历所有可能的起始位置j,并计算哈希值。 对于每个L,总时间复杂度为O(n*m)。而分搜索需要进行log m次,总时间复杂度为O(n*m*log m)。当n和m较大时,这可能可行。例如,当n=10,m=5e5,总时间为10*5e5*20 ≈1e8,可能勉强可行,但需要优化。 为了优化,可能需要从最大的L=m开始向下检查,而是使用分法。一旦找到满足条件的L,立即返回。这样,最坏情况下时间复杂度为O(n*m^2),但在实际中可能更快,尤其是当最长公共子串较长时。 但显然,当m=5e5时,这样的方法无法处理,因此需要更高效的算法。 这可能意味着,对于较大的m,必须使用基于后缀自动机或后缀组的方法,其时间复杂度较低。 回到后缀自动机的思路,构建广义后缀自动机可能更高效。例如,对于每个扩展后的字符串t_i,构建其后缀自动机,并记录每个状态的出现情况。然后,找到在所有自动机中都存在的状态,并记录最长的长度。 但如何合并多个后缀自动机的状态信息?这可能需要复杂的处理,比如使用哈希映射来记录每个子串的出现次。 或者,可以采用逐个处理字符串的方法: 1. 初始化一个空的哈希集合,保存当前所有可能的公共子串的哈希值。 2. 对于第一个字符串t_0,计算所有长度为L的候选子串的哈希值,并存入集合S。 3. 对于后续的每个字符串t_i,计算其所有长度为L的子串的哈希值,并保留与S的交集。如果交集为空,则L可行。 4. 重复这个过程,直到处理完所有字符串,并检查最终的交集是否非空。 但这种方法需要多次处理每个字符串的同L值,时间复杂度可能很高。 综上所述,可能难以找到一个高效的算法来处理n个环形字符串的最长公共子串问题,尤其是在m较大的情况下。因此,可能需要问题中的n或m的范围较小,或者用户的问题存在其他约束条件。 此外,用户提供的引用内容中,引用3提到华为OD机试的题目,其中处理环形字符串时,可能采用了一些巧妙的位掩码或状态记录的方法。例如,在引用3的问题中,需要统计'l','o','x'的出现次是否为偶,这可能通过状态压缩来处理。可能类似的思路可以应用到当前问题中。 然而,当前问题与引用3的问题同,用户需要的是最长公共子串,而是特定字符的出现次。因此,可能需要同的方法。 现在,回到用户的问题,可能需要给出一个基于滚动哈希和分法的解决方案,尽管其时间复杂度可能较高,但在实际中可能适用于较小的m值。 例如,C++代码的大致步骤可能如下: 1. 预处理每个环形字符串s_i,生成t_i = s_i + s_i。 2. 使用分法确定最大的L,使得存在一个长度为L的子串,在所有t_i中出现,并L <=m。 3. 对于每个L,使用滚动哈希计算每个t_i的所有长度为L的子串的哈希值,并存入哈希集合。然后,计算所有集合的交集,若存在公共哈希,则L可行。 4. 返回最大的L作为答案。 在代码实现中,需要注意以下几点: - 滚动哈希的基和模选择,以避免哈希冲突。可能需要使用双哈希(即两个同的基和模)来减少冲突概率。 - 处理环形字符串时,如何确保子串长度超过m。例如,在t_i中,只考虑起始位置j在0到m-1范围内的子串,因为当j超过m时,子串会重复原来的起始位置。例如,起始位置j的范围是0 <= j <m,这样长度为L的子串在t_i中对应的环形子串是唯一的。 因此,在生成哈希值时,只需要处理j从0到m-1,每个长度为L的子串。 这样,对于每个字符串t_i,长度为L的子串量为m(因为j的范围是0到m-1),因此总共有m个子串需要处理,而是2m-L+1。这可以大大减少计算量。 例如,对于每个t_i = s_i + s_i,我们只处理j in 0..m-1,并长度为L的子串t_i.substr(j, L)。如果j + L > m,则子串将包括s_i的末尾部分和开头部分,这正是环形结构所需要的。 因此,在代码中,对于每个L,遍历每个字符串的每个起始位置j in 0..m-1,计算其对应的哈希值。 这可以将每个字符串的处理量减少到m次,而是2m-L+1次,从而降低时间复杂度。 此外,使用分法确定L的最大值,从L=m开始向下尝试,一旦找到满足条件的L,立即返回,避免必要的计算。 因此,代码的大致结构如下: - 预处理每个字符串s_i,生成t_i = s_i + s_i. - 对L从m到1进行循环: - 对于每个字符串s_i,计算其所有起始位置j in 0..m-1的长度为L的子串的哈希值,并存入一个哈希集合中。 - 计算这些哈希集合的交集。如果交集非空,则返回L作为答案。 - 一旦找到,即可停止。 这样的方法在最坏情况下需要O(m*n*m) = O(n*m^2)的时间,这对于m=5e5来说显然可行。因此,这种方法仅适用于较小的m值。 因此,可能用户的问题中的m和n的值较小,或者需要更高效的算法。 但根据用户提供的引用3中的例子,输入字符串的长度可达5e5,所以显然需要线性或接近线性的算法。 这提示可能需要另一种思路,比如利用字符串的循环性质,将问题转换为线性问题,或者寻找每个字符串的最小循环节,从而找到公共的循环模式。 例如,如果每个字符串的循环节相同,那么最长公共子串可能是循环节的重复。但这种情况较为特殊,可能无法推广到所有情况。 另一种可能的观察是,最长公共子串必须出现在所有字符串的某个循环排列中。因此,可以将问题转换为寻找所有字符串的循环排列中的公共子串。但这仍然难以处理。 回到问题本身,用户需要的是C++代码实现。可能需要结合现有的算法,并针对环形结构进行调整。 例如,对于两个环形字符串的最长公共子串,可以将它们扩展为两倍长度,然后使用动态规划的方法。对于多个字符串,可能需要逐步比较。 但动态规划的方法对于多个字符串来说,时间复杂度会很高,因为状态空间会随着字符串量增加而指级增长。 因此,可能需要寻找其他方法。 或许,可以借鉴最长公共子串的哈希方法,采用分法结合滚动哈希,并对每个字符串预处理所有可能长度的哈希值。 例如,对于每个字符串s_i,预处理所有可能的子串的哈希值,并按照长度存储。然后,对于每个可能的L,从大到小,检查是否每个字符串s_i都存在至少一个长度为L的子串(考虑环形),并这些子串是相同的。 这可能通过哈希的交集来实现。但如何高效预处理所有可能的L呢? 这似乎太可能,因为预处理所有L的哈希值需要O(m^2)的存储空间,这在m较大时可行。 因此,可能需要回到分法,每次处理一个特定的L值,并计算所有字符串的哈希集合,然后交集。 为了优化时间,可以在处理每个L时,对于每个字符串s_i,预先计算所有长度为L的子串的哈希值,并存储在一个哈希集合中。然后,计算所有哈希集合的交集。 但即使如此,当L较大时,每个字符串的处理时间为O(m),所以总时间为O(n*m) per L。如果使用分法,总时间为O(n*m*log m),这在m=5e5n较小时,比如n=2,可能勉强可行,但需要高效的代码实现。 例如,对于n=2,m=5e5,总时间为2*5e5*20=2e7 operations,这在C++中可能在可接受的时间范围内。 因此,代码可能如下: 使用双哈希减少冲突概率: 对于每个子串,计算两个同的哈希值,使用同的基和模。这样,两个哈希值共同作为键,减少冲突的可能。 预处理每个字符串的扩展版本: t_i = s_i + s_i. 对于每个L,从m递减到1: 收集所有字符串的哈希集合: for each string s_i: compute all hashes of length L in t_i, starting at positions 0..m-1 store these hashes in a set 计算所有集合的交集: result_set = intersection of all sets if result_set is not empty: return L 因此,如何高效地计算这些集合的交集?可以将第一个字符串的集合作为基准,然后依次与其他集合取交集。如果中途交集为空,则跳过后续字符串的处理。 这在代码中可以优化,减少必要的计算。 具体到C++代码,可能的实现步骤: 1. 对于输入的n个字符串,每个字符串s,生成t = s + s. 2. 对于L从m down to 1: a. 初始化一个全局的哈希集合,初始为空。 b. 对于每个字符串t_i: i. 计算所有起始位置j in 0..m-1的长度为L的子串的哈希值。 ii. 将这些哈希值存入一个临时的哈希集合temp_set. iii. 如果是第一个字符串,将temp_set赋给global_set. iv. 否则,计算global_set和temp_set的交集,并更新global_set. v. 如果global_set为空,break并跳过后续字符串的处理。 c. 如果global_set非空,返回L作为答案. 3. 如果没找到任何L,返回0. 这种方法的优化点在于,一旦某个字符串的temp_set与global_set的交集为空,即可提前终止当前L的处理,进入下一个L的循环。 在C++中,可以使用unordered_set来存储哈希值,并使用取交集的方法。但unordered_set的交集操作需要遍历较小的集合,并检查每个元素是否存在于另一个集合中。这可能效率较低,但可以通过一些优化,比如始终保留global_set为较小的集合,或者使用排序后的vector和分查找来加速。 例如,将每个temp_set存储为排序后的vector,这样在取交集时可以使用双指针法,时间复杂度为O(k log k)排序加上O(k)的交集查找,其中k是每个集合的元素量。 这可能比使用unordered_set更高效,因为交集操作的时间复杂度较低。 因此,代码可能的优化步骤: - 对于每个temp_set,将哈希值存储为排序后的vector. - 在取交集时,使用双指针法遍历两个已排序的vector,找出公共元素。 - 这样可以高效地计算交集。 这可能会提高性能,尤其是当哈希集合较大时。 此外,为了处理双哈希,可以将每个子串的哈希值表示为一个pair<uint64_t, uint64_t>,其中两个值分别对应两个同的哈希函的输出。 综上所述,C++代码的大致结构可能如下: 预处理每个字符串的扩展版本,然后对于每个可能的L,从大到小,检查所有字符串是否存在公共的哈希值。 现在,针对用户的问题,需要编写这样的代码。 然而,用户问题中的“边权和”可能还需要进一步明确。如果边权子串长度,而是其他定义,例如每个字符的权值之和,那么问题可能更复杂。例如,需要找到公共子串,并其权值和最大。这可能需要同的处理方法,例如动态规划或贪心算法。 但用户的问题描述中并未给出边权的具体定义,因此可能需要假设边权即子串长度,即最长公共子串长度即为所的最大边权和。 综上,最终的C++代码将基于分法或递减L的循环,结合滚动哈希和集合交集检查来实现。但由于时间和空间复杂度的问题,可能需要针对具体情况进行优化。</think>### 解决环形字符串最长公共子串问题的C++实现思路 #### 问题分析 需要找到n个环形字符串的最长公共子串,并最大化边权和(假设边权为子串长度)。环形字符串的处理可通过将字符串扩展为双倍长度(如`t = s + s`),从而将环形问题转化为线性问题。最长公共子串需满足以下条件: 1. 在所有扩展后的字符串中出现; 2. 长度超过原字符串长度m; 3. 子串的哈希值在所有字符串的哈希集合中存在交集。 #### 算法设计 1. **环形处理**:将每个字符串扩展为`t = s + s`; 2. **滚动哈希**:使用双哈希(两个同的基和模)减少冲突; 3. **递减搜索**:从最大可能长度L=m开始,依次递减检查; 4. **哈希交集**:对每个L,计算所有字符串的哈希集合的交集,若存在则返回L。 #### C++代码实现 ```cpp #include <iostream> #include <vector> #include <string> #include <algorithm> #include <unordered_set> #include <utility> using namespace std; using HashPair = pair<uint64_t, uint64_t>; // 双哈希配置 const uint64_t BASE1 = 911382629; const uint64_t BASE2 = 3571428571; const uint64_t MOD1 = 1e18 + 3; const uint64_t MOD2 = 1e18 + 7; // 预处理前缀哈希和幂 void precompute_hashes(const string& s, vector<uint64_t>& prefix1, vector<uint64_t>& prefix2, vector<uint64_t>& power1, vector<uint64_t>& power2) { int n = s.size(); prefix1.resize(n + 1); prefix2.resize(n + 1); power1.resize(n + 1); power2.resize(n + 1); prefix1[0] = prefix2[0] = 0; power1[0] = power2[0] = 1; for (int i = 0; i < n; ++i) { prefix1[i + 1] = (prefix1[i] * BASE1 + s[i]) % MOD1; prefix2[i + 1] = (prefix2[i] * BASE2 + s[i]) % MOD2; power1[i + 1] = (power1[i] * BASE1) % MOD1; power2[i + 1] = (power2[i] * BASE2) % MOD2; } } // 获取子串的双哈希值 HashPair get_hash(const vector<uint64_t>& prefix1, const vector<uint64_t>& prefix2, const vector<uint64_t>& power1, const vector<uint64_t>& power2, int l, int r) { uint64_t hash1 = (prefix1[r] - prefix1[l] * power1[r - l] % MOD1 + MOD1) % MOD1; uint64_t hash2 = (prefix2[r] - prefix2[l] * power2[r - l] % MOD2 + MOD2) % MOD2; return {hash1, hash2}; } int longest_common_substring(const vector<string>& strs) { int n = strs.size(); if (n == 0) return 0; int m = strs[0].size(); // 预处理所有字符串的扩展版本和哈希 vector<vector<uint64_t>> prefix1(n), prefix2(n), power1(n), power2(n); vector<string> extended_strs(n); for (int i = 0; i < n; ++i) { extended_strs[i] = strs[i] + strs[i]; precompute_hashes(extended_strs[i], prefix1[i], prefix2[i], power1[i], power2[i]); } // 从最大可能长度开始递减搜索 for (int L = m; L >= 1; --L) { vector<unordered_set<HashPair>> hash_sets(n); // 收集每个字符串的所有可能哈希值 for (int i = 0; i < n; ++i) { const string& t = extended_strs[i]; unordered_set<HashPair> current_set; for (int j = 0; j < m; ++j) { // 只需处理原字符串范围内的起始点 int r = j + L; if (r > t.size()) break; // 超出扩展字符串范围 HashPair h = get_hash(prefix1[i], prefix2[i], power1[i], power2[i], j, r); current_set.insert(h); } if (current_set.empty()) break; // 当前L可能满足 hash_sets[i] = move(current_set); } // 计算所有哈希集合的交集 unordered_set<HashPair> common = hash_sets[0]; for (int i = 1; i < n && !common.empty(); ++i) { unordered_set<HashPair> temp; for (const auto& h : common) { if (hash_sets[i].count(h)) temp.insert(h); } common = move(temp); } if (!common.empty()) return L; } return 0; } int main() { vector<string> strs = {"alolobo", "looxdolx", "bcbcbc"}; // 示例输入 cout << "Max length: " << longest_common_substring(strs) << endl; return 0; } ``` #### 代码说明 1. **双哈希处理**:使用两个同的哈希函减少冲突概率; 2. **前缀哈希预处理**:加速任意子串的哈希计算; 3. **递减长度检查**:从最长可能子串开始验证,找到即返回; 4. **哈希集合交集**:通过逐步筛选公共哈希值判断是否存在公共子串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值