「一本通 2.2 例 2」Power Strings

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

用 KMP 失败函数求最长 border,最小正周期 p = n - pi[n-1];p 整除 n 时答案为 n/p,否则为 1。

OJ: roj

题目 ID: 1466

难度:普及

标签:字符串KMP

创建: 2026-09-30 12:33

更新: 2026-10-06 00:18

题目描述

给定若干个仅含英文字母的字符串(以单独一行 . 结束输入),对每个字符串求最大重数 kk,使其能由某个子串重复 kk 次连接而成。字符串长度 ≤106\le 10^6。

思路

KMP 求失败函数得到最长 border 长度 bb,最小正周期 p=n−bp = n - b。若 p∣np \mid n 则答案为 n/pn/p,否则为 11。

参考代码

Python 版:

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:33
# update_at: 2026-09-30 12:33

import sys

STOP = b"."  # 题面的结束标记,单独占一行


def power_count(s: bytes) -> int:
    """返回 s 的最短周期的重数 len(s)//p;若最短正周期分不尽 s 则返回 1。

    KMP 失败函数:pi[i] = s[:i+1] 的最长「真前缀 = 真后缀」长度。
    len(s) - pi[-1] 是 s 的最小正周期,只有当它是 len(s) 的约数时周期才整分 s。
    """
    n = len(s)
    pi = [0] * n               # 失败函数数组,与 s 等长
    border = 0                 # 已匹配的 border 长度,即 pi[i-1] 的滚动值
    for i in range(1, n):
        ch = s[i]
        while border and s[border] != ch:  # 失配就沿 border 链回退
            border = pi[border - 1]
        if s[border] == ch:    # 配上了,border 变长
            border += 1
        pi[i] = border
    p = n - border
    return n // p if n % p == 0 else 1


def solve() -> None:
    out: list[str] = []
    for line in sys.stdin.buffer.read().split():
        if line == STOP:
            break                  # 结束行之后不再有数据
        out.append(str(power_count(line)))
    sys.stdout.write('\n'.join(out) + '\n')


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * 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-06 00:18
 * update_at: 2026-10-06 00:18
 */
#include <iostream>
#include <cstring>
using namespace std;

typedef long long ll;

const int MAXN = 1000005; // 单串最大长度

char s[MAXN];
int pi[MAXN]; // 失败函数:pi[i] 为前缀 s[0..i] 的最长 border 长度

// 返回字符串 s[0..n-1] 的最大重复重数
int power_count(int n) {
    for (int i = 0; i < n; ++i) pi[i] = 0;
    int border = 0; // 当前已匹配的 border 长度
    for (int i = 1; i < n; ++i) {
        char ch = s[i];
        while (border && s[border] != ch) // 失配则沿 border 链回退
            border = pi[border - 1];
        if (s[border] == ch) // 匹配成功,border 延长
            ++border;
        pi[i] = border;
    }
    int p = n - border; // 最小正周期
    return (n % p == 0) ? (n / p) : 1;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    while (cin >> s) {
        if (s[0] == '.' && s[1] == '\0') break; // 单独一行 '.' 结束输入
        int n = strlen(s);
        cout << power_count(n) << '\n';
    }
    return 0;
}