ABC137 E - Coins Respawn
問題原文
問題要旨
頂点1から頂点 まで移動できることが保証される、 頂点 辺の有向グラフが与えられる。
このグラフ上で、頂点1から頂点 まで移動するゲームを考える。
プレイヤーは1秒につき一回移動でき、各辺を通るごとに何度でも 点を得ることができる。
最終的に頂点 にたどり着いたときの、 (点数の総和)- ( × 秒数)がスコアになる。(スコアの最低値は0)
達成できるスコアの最大値を求めよ。またスコアを無限大に大きくできる場合には−1とせよ。
解法
1秒間につき1回移動できるので、各辺を通った時に得ることができる点数は、 点としてよい。
またスコアが最大になるように移動するので、頂点1 から頂点 までの最長経路を求めればよい。
これは、各辺の重みの正負を反対にした上で、最短経路問題を解けばいい。
今回は負の重みがある辺が混ざってくるので、ダイクストラ法でなくベルマンフォード法を使う。
ただし、頂点1から頂点 上に存在しない負の閉路を検知してループを止めないよう気を付ける必要がある。
負の閉路の影響が頂点 まで伝播しうる場合、最大追加で 回ループを回せばその影が届く。よって、 回目以降に最短距離が更新されるようなものについては、 その距離を-∞としてしまってよい。(これを素直に更新後の値にすると、うまくいかない。あくまでも 負閉路の影響が頂点 に伝播するかどうか をチェックする。)
この点に関しては、以下の記事でとても丁寧にまとめられています。 (大変勉強になりました、ありがとうございます)
実装
N, M, P = map(int, input().split()) Edges = [] for i in range(M): a, b, c = map(int, input().split()) a, b = a - 1, b - 1 Edges.append([a, b, -(c - P)]) dist = [float('inf')] * N dist[0] = 0 for i in range(2 * N): for fr, to, cost in Edges: if dist[to] > dist[fr] + cost: dist[to] = dist[fr] + cost if i >= N: dist[to] = -float('inf') print(max(0, -dist[N - 1]) if -dist[N - 1] != float('inf') else -1)
感想
最長経路は重みを正負逆転した上での最短経路 、というのはよく見る典型ですね。
コンテスト後に嘘を落とすhackケースが指摘され、話題になりました。
D - Score Attack も同様の点に注意しないと本来は落ちるので、気をつけましょう。
勉強になりました。