E - 山頂コレクション / Peak Collection Editorial by admin
GPT 5.2 High概要
山頂を左から順に見ていき、標高が狭義増加するように高々 \(K\) 個選び、合計入山料が \(B\) 以下になる範囲で「選べる山頂数の最大値」を求めます。
考察
この問題は「番号の昇順に進む」「選んだ山頂の標高が増加」という条件から、増加部分列(LIS)に似た形をしています。ただし今回は「長さ最大」だけでなく、各山頂にコスト \(C_i\) があり、合計が予算 \(B\) 以下という制約もあります。
- 素朴に「選ぶ/選ばない」を全探索すると \(2^N\) となり、\(N \le 500\) では到底間に合いません。
- 「標高でソートして…」のような工夫をしても、山頂番号の順に進む制約(\(i_1 < i_2 < \cdots\))があるため、結局は「前にある山頂からつなぐ」形のDPが自然です。
- ここで重要な観察は、“長さ \(l\) の増加列を作るための最小コスト”を持てば、予算 \(B\) 以内かどうかを判定できる、という点です。
つまり「最大長」問題を、「各長さに対して最小コストを求める」問題に変換します。
アルゴリズム
DPを次のように定義します。
- \(dp[i][l] =\) 「山頂 \(i\) を最後に選ぶ、長さ \(l\) の標高増加列」を作るために必要な入山料合計の最小値
(作れない場合は \(\infty\) 扱い)
遷移はLISと同様に「1つ前の山頂 \(j\) からつなぐ」形です。
初期化
- 山頂 \(i\) を単独で選ぶ:
\(dp[i][1] = C_i\)
- 山頂 \(i\) を単独で選ぶ:
遷移
- \(j < i\) かつ \(S_j < S_i\) のとき、山頂 \(j\) の列の後ろに \(i\) を付けられるので
\(dp[i][l] = \min\left(dp[i][l],\ dp[j][l-1] + C_i\right)\)
- ただし、予算判定を簡単にするため、\(B\) を超えたら \(\infty\)(コードでは \(B+1\))として扱います。
- \(j < i\) かつ \(S_j < S_i\) のとき、山頂 \(j\) の列の後ろに \(i\) を付けられるので
答えの更新
- どこかの \(i\) について \(dp[i][l] \le B\) が成り立てば、長さ \(l\) は実現可能です。
その最大の \(l\)(ただし \(l \le K\))を答えにします。
- どこかの \(i\) について \(dp[i][l] \le B\) が成り立てば、長さ \(l\) は実現可能です。
具体例(イメージ)
例えば「長さ2で終点が \(i\)」を作りたいときは、 - \(i\) より左にある山頂 \(j\) の中で - 標高が \(S_j < S_i\) を満たし - かつ \(dp[j][1] + C_i\) が最小になるもの を探せばよい、ということになります。これを長さ \(3,4,\dots\) に拡張したのが今回のDPです。
計算量
- 時間計算量: \(O(N^2 K)\)
(各 \(i\) について全ての \(j<i\) を見て、長さ \(2..K\) を更新) - 空間計算量: \(O(NK)\)
(\(dp\) 配列)
制約内では \(500^2 \times 50 = 12{,}500{,}000\) 程度なので十分間に合います。
実装のポイント
\(\infty\) は「予算 \(B\) を超えた」状態として扱えればよいので、コードでは
INF = B + 1としています(これで<= B判定が簡単)。登頂数の上限は \(K\) ですが、当然 \(N\) を超えられないため
L = min(K, N)としています。dp[j][l-1]がすでに> Bの場合、それ以上足しても無駄なのでスキップし、定数倍を軽くしています。答えは「どの終点でもよい」ので、各 \(i\) ごとに
dp[i][l] <= Bを見て最大の \(l\) を更新します。ソースコード
import sys
def main():
input = sys.stdin.readline
N, K, B = map(int, input().split())
C = [0] * N
S = [0] * N
for i in range(N):
c, s = map(int, input().split())
C[i] = c
S[i] = s
L = min(K, N)
INF = B + 1
dp = [[INF] * (L + 1) for _ in range(N)]
ans = 0
for i in range(N):
ci = C[i]
dpi = dp[i]
dpi[1] = ci
if ci <= B:
ans = max(ans, 1)
si = S[i]
for j in range(i):
if S[j] < si:
dpj = dp[j]
# length 2..L
for l in range(2, L + 1):
prev = dpj[l - 1]
if prev <= B:
val = prev + ci
if val > B:
val = INF
if val < dpi[l]:
dpi[l] = val
for l in range(2, L + 1):
if dpi[l] <= B and l > ans:
ans = l
print(ans)
if __name__ == "__main__":
main()
この解説は gpt-5.2-high によって生成されました。
posted:
last update: