跳转至

BISHI88 小苯的魔法染色

中等通过率 26.42%python3样例通过牛客 AC贪心二分字符串

牛客原题  源码

讲解章节二分

一句话

至多 m 次区间覆盖,每次长度 <= k,把所有 'W' 盖住,求最小 k。

解题思路

这题考什么

二分答案 + 贪心判定。k 越大越容易完成,可行性对 k 单调,于是二分 k。 二分答案见 44-二分,区间覆盖贪心见 47-贪心

判定 check(k):从左往右扫,遇到第一个还没被盖住的 'W'(设在位置 p), 最优做法一定是把区间放成 [p, p+k-1]——起点再往左只会浪费长度, 往右就盖不住 p。于是贪心地放一段、跳到 p+k 之后继续找下一个未覆盖的 'W', 统计用了几段,段数 <= m 即可行。这个贪心是「最少区间覆盖点集」的标准结论。

数据规模与复杂度

n <= 2e5。判定 O(n)(每次只在 W 位置列表上跳,用 bisect 更快, 但线性扫已经够),二分 log n ≈ 18 次,总计约 3.6e6,稳过。 这里把所有 W 的下标先收集成数组,判定时用 bisect 跳到下一个未覆盖的 W, 单次判定降到 O(段数 * log n),比逐格扫更快。

坑在哪

  1. 若字符串本身全是 'R'(无 W),答案是 0 而不是 1。 题面「输出一个正整数」是假的:实测数据里有全 R 的测试点,期望输出 0。 一次都不用施法,最小的 k 自然是 0;
  2. m <= n 保证了 k = n 一定可行(一次盖全),二分右端取 n 即可;
  3. 「至多 m 次」——用不满不扣分,判定写 <= m 而不是 == m;
  4. 输入的字符串单独一行且不含空格,用 split() 按 token 取正好是一整个串。

样例复核

n=5, m=2, s="WRWWR",W 在下标 0,2,3。 k=2: 盖 [0,1],下一个未覆盖 W 是 2,盖 [2,3],共 2 段 <= 2 ✓; k=1: 需要 3 段 > 2 ✗。答案 2,与样例一致。

参考实现

solutions/BISHI88.py
import sys
from bisect import bisect_left


def main() -> None:
    data = sys.stdin.buffer.read().split()
    n, m = int(data[0]), int(data[1])
    s = data[2]                                # 整行字符串不含空格,正好是一个 token
    W = ord('W')                               # bytes 取下标得到 int,先把 'W' 转成字节值
    pos = [i for i in range(n) if s[i] == W]    # 所有待染格的下标
    if not pos:
        sys.stdout.write("0\n")                # 全是 R,一次都不用施法
        return

    total = len(pos)

    def ok(k: int) -> bool:
        """单次长度上限为 k 时,贪心放段能否在 m 次以内盖住所有 W。"""
        used = 0
        i = 0
        while i < total:
            used += 1                          # 新开一段,左端点钉在当前未覆盖的 W 上
            if used > m:
                return False                   # 段数已超限,不必再往后扫
            # 本段覆盖 [pos[i], pos[i]+k-1],跳到第一个不在该区间的 W
            i = bisect_left(pos, pos[i] + k, i + 1)
        return True

    # 二分最小可行的 k,循环不变量:答案始终落在闭区间 [lo, hi] 内
    lo, hi = 1, n                              # k=n 必可行(m>=1,一段盖全)
    while lo < hi:
        mid = (lo + hi) // 2
        if ok(mid):
            hi = mid                           # mid 可行,答案不会比它更大
        else:
            lo = mid + 1                       # mid 不可行,答案至少是 mid+1
    sys.stdout.write("%d\n" % lo)


main()
[:octicons-arrow-left-16: BISHI87](BISHI87.md) [BISHI89 :octicons-arrow-right-16:](BISHI89.md)