「一本通 2.2 例 1」剪花布条

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

用 KMP 匹配模式串,每次匹配成功立即计数并清零匹配指针,保证子串互不重叠。

OJ: roj

题目 ID: 1465

难度:普及-

标签:字符串KMP贪心

创建: 2026-09-30 12:30

更新: 2026-10-06 00:19

题目描述

输入多组测试数据,每组一行,由空格分开的主串和模式串。求主串中最多能剪出多少个互不重叠的完整模式串。输入中单独一行 # 表示结束(字符串内部或开头的 # 不算结束)。

数据范围:字符串长度 ≤ 1000。

思路

从左到右扫描主串,用 KMP 匹配模式串;一旦完整匹配成功,答案加 1 并把匹配指针清零,确保剪出的子串互不重叠。

参考代码

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

import sys


def count_non_overlapping(text: str, pattern: str) -> int:
    """计算模式串在主串中互不重叠出现的最大次数(KMP 匹配与贪心跳跃)。"""
    m = len(pattern)
    pi = [0] * m
    j = 0
    for i in range(1, m):
        while j > 0 and pattern[i] != pattern[j]:
            j = pi[j - 1]
        j += pattern[i] == pattern[j]
        pi[i] = j

    ans = 0
    j = 0
    for ch in text:
        while j > 0 and ch != pattern[j]:
            j = pi[j - 1]
        j += ch == pattern[j]
        if j == m:
            ans += 1
            j = 0  # 剪下饰条后不可重叠,重置匹配进度
    return ans


def solve() -> None:
    lines = sys.stdin.read().splitlines()
    out: list[str] = []
    for line in lines:
        if line == "#":
            break
        parts = line.split()
        if not parts:
            continue
        text, pattern = parts[0], parts[1]
        out.append(str(count_non_overlapping(text, pattern)))
    print("\n".join(out))


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:19
 * update_at: 2026-10-06 00:19
 */
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXL = 1010;          // 字符串最大长度
char s[MAXL], t[MAXL];          // s 为主串,t 为模式串
int nxt[MAXL];                  // nxt[i] 表示模式串前缀 t[0..i] 的最长相等真前后缀长度

// 预处理模式串 t 的 nxt(前缀函数)数组
void get_next(char t[], int m) {
    int j = 0;
    nxt[0] = 0;
    for (int i = 1; i < m; i++) {
        while (j > 0 && t[i] != t[j]) j = nxt[j - 1];
        if (t[i] == t[j]) j++;
        nxt[i] = j;
    }
}

// KMP 匹配,每匹配到一个完整 t 就计数并将匹配指针清零,保证剪出的子串互不重叠
int kmp_count(char s[], int n, char t[], int m) {
    int j = 0, ans = 0;
    for (int i = 0; i < n; i++) {
        while (j > 0 && s[i] != t[j]) j = nxt[j - 1];
        if (s[i] == t[j]) j++;
        if (j == m) {
            ans++;
            j = 0; // 剪下这一段,从下一字符重新匹配
        }
    }
    return ans;
}

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