A - 山登りトレーニング / Mountain Climbing Training 解説 by admin
Qwen3-Coder-480B概要
高橋君は現在の標高 \(P\) から始めて、他の会員に師事することで自分の到達可能標高を上げていきます。1回の師事にはコスト \(C\) がかかり、最終的に標高 \(T\) 以上にしたいとき、必要な最小コストを求めます。
考察
まず、高橋君が目標の標高 \(T\) に到達できないケースとはどのようなときでしょうか?
- 会員の中に、高橋君の現在の標高 \(P\) より高く、かつ \(T\) 以上の標高に到達できる人がいない場合、どうやっても目標を達成できません。このようなときは
-1を出力します。
次に、目標を達成できる場合を考えましょう。
高橋君は、自分の現在の標高よりも高い会員にしか師事できません。したがって、最初に考えられるのは「自分より高い標高を持つ会員」の中から、できるだけ高いところまで一気に上がるという戦略です。
しかし、このままでは無駄があるかもしれません。例えば、途中で高い会員に師事しても、結局あとでそれより低いけど十分高い会員に師事すればよかった、というケースがあります。
そこで重要な観察があります:
師事する順序は、対象となる会員の標高の昇順に限っても一般性を失いません。
さらに、最終的に得た標高列のうち、本当に必要だったのは「一番最後に師事した人」だけです。
したがって、以下のような貪欲な戦略が有効です:
- 高橋君より高い標高を持つ会員をすべてリストアップし、降順にソートする。
- 大きい方から見ていき、「まだ目標 \(T\) に届かないならその人に師事する」という操作を繰り返す。
- 各ステップで、師事した相手の標高を記録しておき、後で「本当に必要だったか」を判定する。
このようにして、最小回数(=最小コスト)で目標を達成できます。
また、素朴な全探索などで全ての組み合わせを試すと、最悪の場合 \(O(2^N)\) の計算量となり、間に合いません。今回の制約では最大 \(N = 2 \times 10^5\) なので、線形あるいはそれに近い計算量のアルゴリズムが必要です。
アルゴリズム
- 高橋君自身の標高 \(P\) がすでに \(T\) 以上であれば、何もせずに
0を出力。 - そうでなければ、会員の標高リスト \(S\) から、\(P\) より大きいものだけを取り出し、昇順にソートしておく。
- これを大きい順に見ながら、以下の処理を行う:
- 現在の標高がまだ \(T\) 未満なら、その会員に師事(=現在の標高を更新+コスト加算)。
- 師事した会員の標高を優先度付きキュー(最大ヒープ)に入れる。
- 最後に、実際に目標を達成できたかどうかを確認し、達成できていれば合計コストを出力。できなければ
-1を出力。
このとき、優先度付きキューを使いながら最小コストでの更新を管理することで、無駄な操作を避けることができます。
計算量
- 時間計算量: \(O(N \log N)\)
(ソートに \(O(N \log N)\)、ヒープ操作に \(O(N \log N)\)) - 空間計算量: \(O(N)\)
(候補リストおよびヒープのサイズ)
実装のポイント
優先度付きキューをPythonで扱う際は
heapqモジュールを使用するが、これは最小ヒープなので、最大値を取り出すために負の値を格納する工夫が必要。入力を高速に読み込むために
sys.stdin.read()を使用している。最初に自らが目標を達成しているケースを忘れずにチェックすること。
不可能なケース(誰にも師事できない or 目標を超える標高を持つ人がいない)も正確に判定する。
ソースコード
import sys
import heapq
def main():
import sys
input = sys.stdin.read
data = input().split()
idx = 0
N = int(data[idx]); idx += 1
P = int(data[idx]); idx += 1
T = int(data[idx]); idx += 1
C = int(data[idx]); idx += 1
if N == 1:
if P >= T:
print(0)
else:
print(-1)
return
S = list(map(int, data[idx:idx+N-1]))
# 現在の高橋君の標高
current = P
if current >= T:
print(0)
return
# 師事できる候補(自分より高い標高の会員)
candidates = [s for s in S if s > current]
candidates.sort() # 昇順ソート
# 最大ヒープを模擬するため、負の値を使って最小ヒープを使う
heap = []
cost = 0
actions = 0
# candidatesを大きい方から見ていく
for s in reversed(candidates):
if current >= T:
break
if s <= current:
continue
# 師事する
heapq.heappush(heap, -s) # 最大値を取り出すために負にして格納
current = s
cost += C
actions += 1
# 最後に、heapの中から不要なものを取り除いてコストを最小化
# heapには選択した候補の標高が入っている(負の値)
while heap and (-heap[0]) > current:
heapq.heappop(heap)
# 現在の標高がT以上ならコストを出力
if current >= T:
print(cost)
else:
# heapの中から、currentを維持するために必要な最小回数を選ぶ
# つまり、candidatesの中でcurrent以上のものをソートして前から取る
valid = [s for s in candidates if s >= T]
if not valid:
print(-1)
return
valid.sort()
# 高橋君がvalid[0]まで到達するには何回師事が必要か?
# current < valid[0] なので、current未満のものは使えない
needed = 0
temp_current = P
temp_cost = 0
temp_heap = []
for s in candidates:
if temp_current >= T:
break
if s <= temp_current:
continue
heapq.heappush(temp_heap, -s)
temp_current = s
temp_cost += C
needed += 1
if temp_current >= T:
print(temp_cost)
else:
print(-1)
if __name__ == "__main__":
main()
この解説は qwen3-coder-480b によって生成されました。
投稿日時:
最終更新: