Test

GitHub跳转原题关系图返回列表

枚举三个字符串的拼接顺序,用 KMP 求相邻字符串的最大后缀前缀重叠。

OJ: luogu

题目 ID: CF25E

难度:普及+/提高

标签:KMP最短公共超串全排列python

日期: 2026-07-16 19:57

题意

求包含给定三个字符串作为子串的最短字符串长度。

思路

固定先后顺序后,每次应让左串后缀与右串前缀重叠尽量长。若一串已经包含另一串,合并结果直接取较长者。

否则对 right + separator + left 求前缀函数,最后一个值就是 left 后缀与 right 前缀的最大重叠长度。三个字符串只有 3!=63!=6 种顺序,全部枚举取最短即可。

分隔字节 \0 不会出现在小写字符串中,防止 KMP 匹配跨过边界。

Python 知识

  • itertools.permutations(strings) 直接生成 6 种排列。
  • right in left 使用 Python 底层字符串搜索处理包含关系。
  • min(generator) 不需要保存所有候选超串。
  • bytes 可以直接拼接并用 b"\0" 作为安全分隔符。

代码

python
import sys
from itertools import permutations


def build_prefix(s):
    """计算 KMP 前缀函数(border 数组)"""
    n = len(s)
    pi = [0] * n
    j = 0
    for i in range(1, n):
        # 沿 border 链回退,直到前缀可扩展或到根
        while j > 0 and s[i] != s[j]:
            j = pi[j - 1]
        if s[i] == s[j]:
            j += 1
        pi[i] = j
    return pi


def get_overlap(left, right):
    """
    用 KMP 求 left 后缀与 right 前缀的最长重叠长度。

    把 right 当模式串,在 left 上走一遍自动机,
    扫描结束后 j 就是最长重叠长度。
    调用前保证 right 不在 left 内部,所以 j 永远不会到达 len(right)。
    """
    if not right:
        return 0
    pi = build_prefix(right)
    j = 0  # KMP 自动机的当前状态
    for ch in left:
        while j > 0 and ch != right[j]:
            j = pi[j - 1]
        if ch == right[j]:
            j += 1
    return j


def merge(left, right):
    """
    合并两个串:left 在前,right 在后,
    利用后缀-前缀重叠去掉重复部分,得到包含两者的最短串。
    """
    # 如果其中一个已经包含另一个,直接返回较长的
    if right in left:
        return left
    if left in right:
        return right
    overlap = get_overlap(left, right)
    return left + right[overlap:]


def solve():
    strings = sys.stdin.read().split()
    # 三串的合并顺序有 6 种排列,取最短结果
    answer = min(
        len(merge(merge(a, b), c))
        for a, b, c in permutations(strings)
    )
    print(answer)


if __name__ == "__main__":
    solve()

复杂度

排列数为常数,每次合并线性,总时间 O(s1+s2+s3)O(|s_1|+|s_2|+|s_3|),辅助空间同阶。

总结

三个字符串时,枚举顺序比设计复杂状态更直接;KMP 则把最大重叠从平方比较降为线性。