w 是某周期无限串的子串时,合法长度恰为 w 的周期:用 KMP 失配函数求最长 border,答案即 L − fail[L],一次线性扫描完成。
OJ: roj
题目 ID: 1467
难度:普及+/提高-
标签:KMPborder周期字符串python
创建: 2026-09-30 12:37
更新: 2026-10-07 11:01
形式化题目
给定一个长度为
例如
正解
思路
第一步:把"合法长度"翻译成"周期"。 断言:
- 若
出现在 的位置 起,那么 与 读的是无限串上相距 的两个位置; 以 为周期,两处字符必然相同,所以 是 的周期。 - 反之,若
对所有 成立,从右往左反复利用它得 ,即 恰是 的长度 前缀,当然也是其子串, 合法。
于是答案就是
第二步:用 border 对偶一步求出最小周期。 经典对偶:
KMP 失配函数
以样例 cabcabca(
| 前缀长度 |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 前缀 |
c | ca | cab | cabc | cabca | cabcab | cabcabc | cabcabca |
| 最长 border |
0 | 0 | 0 | 1 | 2 | 3 | 4 | 5 |
| 最小周期 |
1 | 2 | 3 | 3 | 3 | 3 | 3 | 3 |
看最后一列(
w = c a b c a b c a (L = 8)
pre = c a b c a <- w[1..5]
suf = c a b c a <- w[4..8],与 pre 逐位相同
--
位置 4..5 重叠前缀与后缀能这样重叠,等价于整串"错位
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 12:40
# update_at: 2026-09-30 12:40
import sys
def longest_borders(s: bytes) -> list[int]:
"""KMP 失配函数:fail[i] = 前缀 s[:i] 的最长真 border(相等真前后缀)长度。"""
fail = [0] * (len(s) + 1)
k = 0 # 扫描到 i 时,k 恰好是 fail[i]
for i in range(1, len(s)):
while k and s[i] != s[k]:
k = fail[k] # 沿 border 链回退:次长 border 仍是 border
if s[i] == s[k]:
k += 1
fail[i + 1] = k
return fail
def solve() -> None:
data = iter(sys.stdin.buffer.read().split())
L = int(next(data)) # 题面给的字符串长度,与 len(s) 相等
s = next(data) # bytes:下标得到整数,字符比较更快
fail = longest_borders(s)
# 最短周期 = n - 最长 border,它就是能"生成"w 的最短元串长度
print(L - fail[L])
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 02:53
* update_at: 2026-10-05 02:53
*/
// 求最短生成元长度:w 是某周期串的子串,等价于求 w 的最小周期。
// 由「周期–border 对偶」,最小周期 = L − 最长 border,KMP 失配函数一次线性扫描即可。
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
const int MAXN = 1000005;
ll L; // 题面给出的字符串长度
string w; // 待求最小周期的字符串,下标从 0 开始
int fail[MAXN]; // fail[i]:w 的长度 i 前缀的最长 border 长度;值不超过 10^6,用 int 控制内存
// KMP 失配函数:求出每个前缀的最长 border。
void build_fail() {
fail[0] = 0;
int k = 0; // 扫描到 i 时,k 恰好等于 fail[i]
for (int i = 1; i < L; i++) {
while (k > 0 && w[i] != w[k]) {
k = fail[k]; // 沿 border 链回退:次长 border 仍是 border
}
if (w[i] == w[k]) {
k++;
}
fail[i + 1] = k;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> L >> w;
build_fail();
// 答案就是 w 的最小周期,也就是能自我连接生成 w 的最短元串长度
cout << L - fail[L] << "\n";
return 0;
}复杂度
- 时间
: 递推中 每步至多加一,回退总次数不超过增加总次数,均摊常数次比较;相比朴素枚举的最坏 ,收益正是"把重复比较换成一次求出全部 border 信息"。 - 空间
:失配数组与输入串。 时实测约 55MB、0.08s。
总结
本题的全部难度在两个"翻译":先把"