枚举三个字符串的拼接顺序,用 KMP 求相邻字符串的最大后缀前缀重叠。
OJ: luogu
题目 ID: CF25E
难度:普及+/提高
标签:KMP最短公共超串全排列python
日期: 2026-07-16 19:57
题意
求包含给定三个字符串作为子串的最短字符串长度。
思路
固定先后顺序后,每次应让左串后缀与右串前缀重叠尽量长。若一串已经包含另一串,合并结果直接取较长者。
否则对 right + separator + left 求前缀函数,最后一个值就是 left 后缀与 right 前缀的最大重叠长度。三个字符串只有
分隔字节 \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()复杂度
排列数为常数,每次合并线性,总时间
总结
三个字符串时,枚举顺序比设计复杂状态更直接;KMP 则把最大重叠从平方比较降为线性。