[SCOI2015] 国旗计划

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

把环形区间展开复制,预处理最远可达区间并用倍增统计覆盖一圈的最少人数。

OJ: luogu

题目 ID: P4155

难度:省选/NOI-

标签:环形区间贪心倍增python

日期: 2026-07-16 18:28

题意

每名战士覆盖圆周上的一段顺时针区间,且没有区间被另一个区间包含。对每名战士,求强制选择他后覆盖整圈所需的最少战士数。

思路

先把跨越编号终点的区间右端加 M,于是每个环形区间都成为直线区间。排序后再复制一份并整体加 M,从任意原区间开始覆盖一圈都变成向右覆盖长度 M

因为区间之间没有包含关系,按左端排序后右端也严格向右。当前覆盖到 right 时,最优下一步就是选择左端不超过 right 且右端最远的区间。双指针可为每个区间求出这个 next_interval

对后继映射建立倍增表。强制从区间 start 开始,目标是让覆盖右端达到 start_left + M;从大层向小层跳过仍未到目标的区间,即可得到最少人数。

Python 知识

  • 元组列表直接 sort(),会依次按左端、右端和编号排序。
  • 列表推导式把所有区间平移 M 后追加,实现环转链。
  • array("i") 紧凑保存 2n 个下标和整张倍增表。
  • map(previous.__getitem__, previous) 简洁地计算映射自复合。

代码

python
import sys
from array import array


data = iter(map(int, sys.stdin.buffer.read().split()))
n, circumference = next(data), next(data)
intervals = []
for identity in range(n):
    left, right = next(data), next(data)
    if right < left:
        right += circumference
    intervals.append((left, right, identity))
intervals.sort()
intervals += [(left + circumference, right + circumference, identity)
              for left, right, identity in intervals]

size = 2 * n
next_interval = array("i", [0]) * size
right_pointer = 0
for i, (_, right, _) in enumerate(intervals):
    right_pointer = max(right_pointer, i)
    while right_pointer + 1 < size and intervals[right_pointer + 1][0] <= right:
        right_pointer += 1
    next_interval[i] = right_pointer

jump = [next_interval]
for _ in range(1, n.bit_length() + 1):
    previous = jump[-1]
    jump.append(array("i", map(previous.__getitem__, previous)))

answer = [0] * n
for start in range(n):
    limit = intervals[start][0] + circumference
    current = start
    used = 1
    for level in range(len(jump) - 1, -1, -1):
        destination = jump[level][current]
        if intervals[destination][1] < limit:
            current = destination
            used += 1 << level
    answer[intervals[start][2]] = used + 1

print(*answer)

复杂度

排序 O(nlogn)O(n\log n),双指针 O(n)O(n),倍增预处理和全部询问 O(nlogn)O(n\log n);空间 O(nlogn)O(n\log n)

总结

处理环形覆盖时,复制一圈把它展开成直线;处理重复的最优后继时,再用倍增加速。