D - 警備員の配置 / Placement of Security Guards 解説 by admin
Claude 4.6 Opus (Thinking)概要
直線状に並んだ \(N\) 個の区画すべてをカバーするために、与えられた区間(警備員候補)の中から最小個数を選ぶ区間被覆問題(Interval Covering Problem)です。
考察
重要な気づき
この問題は「数直線上の区間 \([1, N]\) を、与えられた複数の区間で完全に覆うとき、最小何個の区間が必要か?」という古典的な貪欲法の問題です。
素朴なアプローチの問題点
すべての警備員の選び方を全探索すると \(2^M\) 通りあり、\(M\) が最大 \(2 \times 10^5\) なので到底間に合いません。
貪欲法による解決
区間被覆問題には、以下の貪欲戦略が最適解を与えることが知られています:
まだカバーできていない最も左の位置をカバーできる区間のうち、右端が最も大きいものを選ぶ。
具体例で考えてみましょう。\(N = 10\) で区間が \([1,3], [2,6], [4,8], [7,10]\) の場合:
- まずカバー済み = \(0\)。位置 \(1\) をカバーしたい → \(L \leq 1\) の区間は \([1,3]\) と \([2,6]\)(\([2,6]\) は \(L=2 > 1\) なので対象外)。\([1,3]\) を選び、カバー済み = \(3\)。
- 位置 \(4\) をカバーしたい → \(L \leq 4\) の区間で \(R\) 最大は \([4,8]\)(\(R=8\))と \([2,6]\)(\(R=6\))。\([4,8]\) を選び、カバー済み = \(8\)。
- 位置 \(9\) をカバーしたい → \(L \leq 9\) の区間で \([7,10]\)(\(R=10\))を選び、カバー済み = \(10\)。
答えは \(3\) 人です。
アルゴリズム
- 区間を左端 \(L_i\) の昇順にソートする。
- 変数
covered(カバー済みの右端、初期値 \(0\))と、区間を走査するポインタiを用意する。 covered < Nの間、以下を繰り返す:- 次にカバーすべき位置は
covered + 1。 - \(L \leq\)
covered + 1を満たす区間を順に見て、その中で \(R\) が最大のものをbestとして記録する。 bestが見つからない(またはbest\(\leq\)covered)なら、カバー不可能なので \(-1\) を出力。- そうでなければ
covered = bestとし、選んだ警備員数を \(1\) 増やす。
- 次にカバーすべき位置は
- ループ終了後、選んだ警備員数を出力する。
ポイント: ソート済みの区間を前から順に見ていくため、ポインタ i は戻ることがなく、全体で各区間を高々 \(1\) 回ずつしか見ません。
計算量
- 時間計算量: \(O(M \log M)\)(ソートが支配的。貪欲法のループ自体は \(O(M)\))
- 空間計算量: \(O(M)\)(区間を格納する配列)
\(N\) が最大 \(10^9\) と非常に大きいですが、ループは区間の数 \(M\) に依存するため問題ありません。
実装のポイント
coveredは「区画 \(1\) ~coveredがカバー済み」を意味し、初期値 \(0\)(何もカバーしていない状態)とする。次にカバーすべき位置はcovered + 1。ソート後にポインタ
iを使って区間を一度だけ走査する(尺取り法的なテクニック)ことで、ループ全体を \(O(M)\) に抑えている。カバーを延長できない場合(
best == -1またはbest <= covered)は不可能と判定して早期に \(-1\) を返す。ソースコード
import sys
def solve():
input_data = sys.stdin.buffer.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
M = int(input_data[idx]); idx += 1
intervals = []
for i in range(M):
L = int(input_data[idx]); idx += 1
R = int(input_data[idx]); idx += 1
intervals.append((L, R))
# Greedy interval covering
# Sort by left endpoint
intervals.sort()
count = 0
covered = 0 # we have covered up to 'covered' (0 means nothing covered yet)
i = 0
while covered < N:
# We need to cover position covered+1
# Find the interval with L <= covered+1 that has the maximum R
best = -1
while i < M and intervals[i][0] <= covered + 1:
if intervals[i][1] > best:
best = intervals[i][1]
i += 1
if best == -1 or best <= covered:
# Can't extend coverage
print(-1)
return
covered = best
count += 1
print(count)
solve()
この解説は claude4.6opus-thinking によって生成されました。
投稿日時:
最終更新: