B - 山を下るハイカー / Hiker Descending the Mountain Editorial by admin Editorial for printing
Qwen3-Coder-480B概要
高橋君が山を下るときに、標高が厳密に減少する順で休憩スポットを訪問し、得られる景観スコアの合計を最大化する問題。
考察
この問題では、以下の2つの制約があります:
- 立ち寄る休憩スポットの標高はすべて現在地 \(X\) よりも厳密に低い必要がある。
- 立ち寄る順番は、標高が厳密に減少する順でなければならない。
まず、条件1を満たさないスポット(\(A_i \geq X\))は無視できます。
次に、残ったスポットを標高の昇順にソートします。こうすることで、左から右へ進むにつれて標高が高くなるように並び替えられ、後は「左から右に選んでいく中で、スコアの合計を最大化する増加部分列」を探す問題に変わります。
これは典型的な最長増加部分列(LIS)の亜種であり、「スコア和最大の増加部分列」を求める問題です。
ナイーブなDP(動的計画法)では、各要素ごとにそれより前のすべての要素を見て更新するため、計算量が \(O(N^2)\) となり、\(N\) が最大 \(2 \times 10^5\) なのでTLEになります。
そこで、BIT(Binary Indexed Tree)やセグメント木を使って高速化します。これにより、ある位置より前での最大スコアを効率的に取得・更新でき、全体で \(O(N \log N)\) にできます。
アルゴリズム
- 条件 \(A_i < X\) を満たす休憩スポットのみを抽出し、標高の昇順にソートします。
- 各休憩スポットに対応する標高を座標圧縮し、BIT上で管理できるようにします。
- BITを用意し、各スポットについて以下を行います:
- そのスポットより標高が低い中での最大スコアをBITから取得。
- そのスコアに現在のスポットのスコアを足した値と、単体のスコアを比較して良い方を採用。
- BITを更新。
- 全体での最大スコアを記録しながら答えを求めます。
このアルゴリズムにより、標高が減少する順に訪れる制約を守りつつ、得られるスコアの和を最大化できます。
計算量
- 時間計算量: \(O(N \log N)\)
- 空間計算量: \(O(N)\)
実装のポイント
標高の大小関係を保ちつつ、座標圧縮を行うことでBITのサイズを小さく保つ。
BITの
updateとqueryは1-indexedで行う必要があることに注意。スポットのスコアが負の場合もあるため、初期値を \(-\infty\) として適切に扱う。
「その場所だけ選ぶ」場合と「前の続きに繋げる」場合の両方を比較して更新すること。
ソースコード
import sys
from bisect import bisect_left, bisect_right
import heapq
def main():
import sys
input = sys.stdin.read
data = input().split()
N = int(data[0])
X = int(data[1])
spots = []
for i in range(N):
a = int(data[2 + 2*i])
b = int(data[2 + 2*i + 1])
if a < X:
spots.append((a, b))
# 標高でソート
spots.sort()
# dp[i] := 標高が低い方からi番目までのスポットを使ってできる最大スコア
# 実際に使うのは座標圧縮されたインデックスに対してのDP
# しかし、この問題では、連続した部分列の中で降順になっているものを選ぶ必要がある
# つまり、標高順にソートした後、部分列でスコアの和を最大化するような増加部分列(ただしスコアの和を最大化)
# => 最長増加部分列(LIS)の変形:最大和LIS
n = len(spots)
if n == 0:
print(0)
return
# 座標圧縮の準備(標高について)
heights = [spot[0] for spot in spots]
unique_heights = sorted(set(heights))
k = len(unique_heights)
# BIT or Segment Tree を使って効率的に処理する
# dp[h] := 標高 h で終わる経路の最大スコア
# ただし、h の範囲が大きいので、座標圧縮 + BIT で管理する
class BITMax:
def __init__(self, size):
self.size = size
self.tree = [-float('inf')] * (size + 1)
def update(self, i, x):
while i <= self.size:
self.tree[i] = max(self.tree[i], x)
i += i & -i
def query(self, i):
res = -float('inf')
while i > 0:
res = max(res, self.tree[i])
i -= i & -i
return res
bit = BITMax(k)
max_score = 0
# スポットを標高順に処理(すでにソート済み)
for a, b in spots:
idx = bisect_left(unique_heights, a) + 1 # 1-indexed
# idx よりも標高が低い中での最大値を取得
prev_max = bit.query(idx - 1)
current = max(b, prev_max + b)
bit.update(idx, current)
max_score = max(max_score, current)
print(max_score)
if __name__ == "__main__":
main()
この解説は qwen3-coder-480b によって生成されました。
posted:
last update: