公式
D - 岩の破壊 / Rock Destruction 解説 by admin
Gemini 3.0 Flash概要
\(N\) 個の岩を順番に破壊する問題です。各岩に対して「ハンマー(硬度を 1 減らす)」か「ダイナマイト(一撃で破壊する)」のいずれかを選択し、合計 \(K\) 回までのダイナマイトを効果的に使って、全岩を破壊する最小ターン数を求めます。
考察
まず、ダイナマイトを使わずにすべてハンマーで破壊する場合を考えます。このとき、岩 \(i\) を破壊するには \(H_i\) ターンかかるため、合計ターン数は \(\sum_{i=1}^{N} H_i\) となります。
次に、ダイナマイトを岩 \(i\) に使用した場合を考えます。
- ハンマーの場合: \(H_i\) ターンかかる
- ダイナマイトの場合: 1 ターンで済む
つまり、ダイナマイトを岩 \(i\) に使うことで、ターン数を \(H_i - 1\) だけ短縮できることになります。ただし、もともとの硬度が \(H_i = 1\) の岩にダイナマイトを使っても、ハンマーと同じ 1 ターンを消費するため、短縮できるターン数は \(0\) です。
最小ターン数を求めるためには、限られた \(K\) 回のダイナマイトを使って、「短縮できるターン数(\(H_i - 1\))」を最大化すればよいことがわかります。
アルゴリズム
- すべての岩をハンマーで叩いた場合の合計ターン数
total_hammer_turnsを計算します。 - 各岩について、ダイナマイトを使ったときに節約できるターン数 \(H_i - 1\) を計算し、リスト(
savings)に格納します。- \(H_i = 1\) の場合は節約分が \(0\) なので、計算に含める必要はありません(含めても結果は変わりません)。
- 節約できるターン数が多い順にリストをソート(降順ソート)します。
- リストの先頭から最大 \(K\) 個分を取り出し、その合計を「最大削減量」とします。
total_hammer_turns - 最大削減量が答えとなります。
計算量
- 時間計算量: \(O(N \log N)\)
- 岩の硬度の合計計算に \(O(N)\)、節約できるターン数のソートに \(O(N \log N)\) かかります。\(N = 2 \times 10^5\) であるため、制限時間内に十分間に合います。
- 空間計算量: \(O(N)\)
- 各岩の硬度や節約分を保持するためのリストに \(O(N)\) のメモリを使用します。
実装のポイント
- 入力の高速化: \(N\) が大きいため、Python では
sys.stdin.read().split()などを用いて一括で入力を読み込むと実行時間を短縮できます。 - スライスの活用:
savings[:k]のようにスライスを用いることで、もし \(K\) が岩の個数より大きくてもエラーにならず、安全に「最大 \(K\) 個」の合計を計算できます。 - 大きな数値: 合計ターン数は非常に大きな数(最大 \(2 \times 10^{14}\) 程度)になることがありますが、Python は標準で多倍長整数をサポートしているため、オーバーフローを気にせず計算できます。
ソースコード
import sys
def solve():
# 入力を一括で読み込み、スペースや改行で分割します。
# sys.stdin.read().split() は大量の入力を効率的に処理するのに適しています。
data = sys.stdin.read().split()
if not data:
return
# N: 岩の個数, K: ダイナマイトの最大使用回数
n = int(data[0])
k = int(data[1])
# h: 各岩の硬度のリスト
# 入力の3番目の要素以降が各岩の硬度 H_i に対応します。
h = list(map(int, data[2:]))
# すべての岩をハンマーのみで破壊した場合の合計ターン数を計算します。
# 各岩 i に対して H_i 回のハンマー叩きが必要です。
total_hammer_turns = sum(h)
# ダイナマイトを岩 i に使用した場合、その岩は1ターンで破壊されます。
# つまり、ハンマーで破壊する場合に比べて (H_i - 1) ターンを節約できます。
# ただし、H_i = 1 の場合はハンマーでもダイナマイトでも1ターンのため、節約分は 0 です。
# 節約できるターン数が 0 より大きいもの(H_i > 1)をリストに抽出します。
savings = []
for val in h:
if val > 1:
savings.append(val - 1)
# 節約できるターン数が多い順にソートします。
# ダイナマイトの使用回数 K に制限があるため、節約効率が良い岩から優先的に使います。
savings.sort(reverse=True)
# 最大で K 個のダイナマイトを使用し、節約できるターンの合計(最大削減量)を求めます。
# Pythonのスライス savings[:k] は、k がリストの長さを超えていても適切に処理されます。
max_reduction = sum(savings[:k])
# 最小ターン数 = (ハンマーのみの合計ターン数) - (ダイナマイトによる最大削減量)
print(total_hammer_turns - max_reduction)
if __name__ == '__main__':
solve()
この解説は gemini-3-flash-preview によって生成されました。
投稿日時:
最終更新: