Official
D - 山岳縦走 / Mountain Traverse Editorial by admin
GPT 5.2 High概要
標高が必ず上がるように一方通行の登山道を辿るとき、山小屋 \(1\) から出発して訪れられる山小屋数の最大値(最長経路長)を求めます。
考察
「よい縦走」では、連続する山小屋の標高が \(P_{v_i} < P_{v_{i+1}}\) を満たす必要があります。つまり、実際に使える登山道は 「低い標高 → 高い標高」へ向かう辺だけ です。
ここで重要な観察は次の通りです。
- 標高 \(P_i\) はすべて異なるので、使える辺(\(P_u < P_v\))だけを残すと、標高が必ず増える方向にしか進めません。
- したがって、このグラフにはサイクルが存在しません(標高が増え続けるのに同じ頂点へ戻れない)。つまり DAG(有向非巡回グラフ) になります。
- DAG 上の「山小屋 1 から始まる最長経路」を求めればよい、という問題に帰着できます。
素朴に「全ての経路を探索する」ような DFS をすると、経路数が指数的に増えうるため \(N, M \le 2 \times 10^5\) では到底間に合いません(TLE)。
そこで、DAG の最長経路は トポロジカル順(ここでは標高の昇順)に DP をすれば線形に計算できます。
アルゴリズム
- 入力された辺 \((U_j, V_j)\) について、条件 \(P_{U_j} < P_{V_j}\) を満たすものだけを採用し、隣接リスト
adjを作る
- 条件を満たさない辺は「よい縦走」では絶対に使えないため捨ててよい
- 山小屋を標高の昇順に並べた順序
orderを作る(これは DAG のトポロジカル順になっています) - DP を定義する
dp[v]= 山小屋 \(1\) から出発して山小屋 \(v\) で終わる「よい縦走」の最大訪問数(到達不可能なら 0)- 初期値:
dp[0] = 1(山小屋 1 のみで終了する縦走がある)、その他は 0
orderの順に各頂点 \(u\) を処理し、辺 \(u \to v\) で遷移する
- もし
dp[u] = Lなら、dp[v]をmax(dp[v], L + 1)で更新
- もし
- DP の最大値が答え
(イメージ例)
標高が小さい順に処理していくので、\(u\) を処理する時点で「\(u\) に到達する最適値」は確定しており、その値を使ってより高い標高の頂点へ更新を伝播できます。
計算量
- 時間計算量: \(O((N+M)\log N)\)
- 標高でソートするのに \(O(N\log N)\)、DP の遷移が辺ごとに高々 1 回で \(O(M)\)
- 空間計算量: \(O(N+M)\)
- 隣接リストと DP 配列の分
実装のポイント
使える辺だけ残す:入力辺すべてを持つ必要はなく、
if P[u] < P[v]: adj[u].append(v)のようにフィルタします(DAG 化でき、無駄な遷移も減ります)。到達不能の管理:
dp[u] == 0の頂点は山小屋 1 から到達できないのでスキップすると効率的です。初期値に注意:問題文より \(k=1\)(山小屋 1 のみ)も許されるため、
dp[0]=1、答えの初期値も 1 になります。入力が大きいので
sys.stdin.buffer.read()で高速に読み込むのが安全です。ソースコード
import sys
def main():
data = sys.stdin.buffer.read().split()
it = iter(data)
N = int(next(it))
M = int(next(it))
P = [int(next(it)) for _ in range(N)]
adj = [[] for _ in range(N)]
for _ in range(M):
u = int(next(it)) - 1
v = int(next(it)) - 1
if P[u] < P[v]:
adj[u].append(v)
order = sorted(range(N), key=lambda i: P[i])
dp = [0] * N
dp[0] = 1
ans = 1
for u in order:
du = dp[u]
if du == 0:
continue
ndu = du + 1
for v in adj[u]:
if dp[v] < ndu:
dp[v] = ndu
if ndu > ans:
ans = ndu
print(ans)
if __name__ == "__main__":
main()
この解説は gpt-5.2-high によって生成されました。