E - 山頂コレクション / Peak Collection Editorial by admin Editorial for printing
Qwen3-Coder-480B概要
この問題は、いくつかの制約(登頂数・標高の狭義単調増加・予算)を満たしながら、登頂する山の数を最大化する動的計画法の問題です。
考察
まず、この問題では以下の3つの条件があります: 1. 登頂する山の数は最大 \(K\) 個。 2. 登頂する山の標高は番号順に狭義単調増加であること。 3. 登頂する山の入山料の合計は \(B\) 円以下。
これらの条件をすべて満たしながら、登頂数 \(m\) を最大化することが目的です。
素朴なアプローチとその問題
最も素朴な方法は、全組み合わせを試すことですが、山の数 \(N\) が最大500あるため、現実的ではありません。また、山は番号順にしか進めないため、「すでに登頂した山より標高が低い山には登頂できない」という制約が重要になります。
この「標高が狭義単調増加」という点に注目すると、最後に登頂した山の標高を管理しておくことで、次の山に登頂できるかどうか判定できます。
解決策:動的計画法(DP)
そこで、以下のようなDPテーブルを考えます:
dp[k][b]:= \(k\) 個の山に登頂し、合計コストが \(b\) 円以下であるような選び方の中で、最後に登頂した山の標高の最小値
- 「標高の最小値」を持つ理由は、より多くの山に登頂できる可能性を残すためです。
- 標高が低い山に登頂済みであれば、その後により高い山に登頂できる選択肢が広がります。
更新方法
各山 \(i\)(入山料 \(c\)、標高 \(s\))に対して、次のようにDPテーブルを更新します:
- 既に \(k-1\) 個登頂し、コスト \(b - c\) で達成可能な状態があり、かつそのときの最後の標高が \(s\) より小さければ、新しい状態 \((k, b)\) でも標高 \(s\) で更新可能です。
ただし、同じ山を複数回選ばないようにするために、逆順(後ろから)に更新する必要があります。
アルゴリズム
- DPテーブル
dp[k][b]を初期化します。dp[0][0] = -1(標高が正のため)、それ以外は \(\infty\)。 - 各山 \(i\) に対して:
- 逆順で \(k = K\) から \(1\) まで、\(b = B\) から \(c\) までループ。
dp[k-1][b - c] < sならば、dp[k][b] = min(dp[k][b], s)で更新。
- 最終的に
dp[k][b] != INFとなる最大の \(k\) を答えとする。
計算量
- 時間計算量: \(O(N \cdot K \cdot B)\)
- 空間計算量: \(O(K \cdot B)\)
※ \(K \leq 50\), \(B \leq 500\) なので、十分高速に動作します。
実装のポイント
DPテーブルの更新は必ず逆順に行うこと。これにより、同じ山を複数回使うのを防ぎます。
dp[0][0] = -1としておくことで、最初の山への遷移が簡単になります。「最後に登頂した山の標高の最小値」を持つのがポイントで、これにより後の選択肢を広げることができます。
ソースコード
def main():
import sys
input = sys.stdin.read
data = input().split()
idx = 0
N = int(data[idx])
idx += 1
K = int(data[idx])
idx += 1
B = int(data[idx])
idx += 1
C = [0] * N
S = [0] * N
for i in range(N):
C[i] = int(data[idx])
idx += 1
S[i] = int(data[idx])
idx += 1
# dp[i][k][b] = 最初のi個の山について、k個登頂し、コスト合計がb以下であるような
# 最後に登頂した山の標高の最小値(不可能ならINF)
INF = float('inf')
# dpは3次元だが、KとBが小さいのでOK
# dp[k][b] = 最後に登頂した山の標高の最小値(可能なら)、k個登頂、コストb
dp = [[INF] * (B + 1) for _ in range(K + 1)]
dp[0][0] = -1 # 標高が正なので-1で初期化
for i in range(N):
c = C[i]
s = S[i]
# 後ろから更新することで同じ山を複数回使わないようにする
for k in range(K, 0, -1):
for b in range(B, c - 1, -1):
prev_b = b - c
# 以前の状態でk-1個選んで、最後の標高がs未満なら遷移可能
if dp[k-1][prev_b] < s:
if dp[k][b] > s:
dp[k][b] = s
# k=0のときはコストだけ更新
for b in range(B, c - 1, -1):
if dp[0][b - c] < s: # 実際にはk=0では標高の制約はないが、dp[0][*] = -1で十分
pass
# 実際にはdp[0][b]は常に-1で良いが、念のため更新しない
# 最大のkを見つける
max_k = 0
for k in range(K + 1):
for b in range(B + 1):
if dp[k][b] != INF:
max_k = max(max_k, k)
print(max_k)
if __name__ == "__main__":
main()
この解説は qwen3-coder-480b によって生成されました。
posted:
last update: