🚲

[ABC137 E] Coins Respawn

2024/03/07に公開

https://atcoder.jp/contests/abc137/tasks/abc137_e

START〜GOAL間にない負閉路は無視する問題。

例1で問題を理解する

  • 辺を通るごとに 1 分かかる
  • 全体で T 分かかる
  • 最後に T 分 × P(10) 枚のコインを支払う

のルールなので二つのルートを計算すると、

ルート 計算 残高
1-2-3 20 + 30 - 2 * 10 30
1-3 45 - 1 * 10 35

となるが、あらかじめ辺のコストから P を引いておくと、

ルート 計算 残高
1-2-3 10 + 20 30
1-3 35 35

計算が簡単になる。

またコストがマイナスになるかもしれないのでベルマン-フォード法を用いる。

注意点

START〜GOAL 間にない負閉路は無視すること。

コード (AC)

N, M, P = gets.split.collect(&:to_i)                               # => [3, 3, 10]
Edges = M.times.collect { gets.split.collect(&:to_i) }             # => [[1, 2, 20], [2, 3, 30], [1, 3, 45]]
START = 1
GOAL  = N

Edges = Edges.collect { |from, to, score| [from, to, score - P] }  # => [[1, 2, 10], [2, 3, 20], [1, 3, 35]]

d = Hash.new(-Float::INFINITY)  # distance
d[START] = 0

(N * 2).pred.times do |i|
  updated = false
  Edges.each do |from, to, score|
    v = d[from] + score
    if v > d[to]
      if i == N.pred
        v = Float::INFINITY     # 関連する経路だけに(本当の値ではなく)∞を伝搬する
      end
      d[to] = v
      updated = true
    end
  end
  unless updated
    break
  end
end

p d[GOAL].infinite? ? -1 : [d[GOAL], 0].max                        # => 35

負閉路を検出したらN周かけて∞を伝搬させる。

関係ない経路を除去する解き方

コード (AC)
N, M, P = gets.split.collect(&:to_i)                               # => [3, 3, 10]
Edges = M.times.collect { gets.split.collect(&:to_i) }             # => [[1, 2, 20], [2, 3, 30], [1, 3, 45]]
START = 1                                                          # => 1
GOAL  = N                                                          # => 3

Edges = Edges.collect { |from, to, score| [from, to, score - P] }  # => [[1, 2, 10], [2, 3, 20], [1, 3, 35]]

# スタートとゴールからたどりつけない辺を除外するか?
if true
  def dfs(edges, from, visited = {})
    unless visited[from]
      visited[from] = true
      edges[from].each { |to| dfs(edges, to, visited) }
    end
    visited
  end

  # スタートから辿りつけない辺を除外する
  if true
    start_to_goal = Hash.new { |h, k| h[k] = [] }
    Edges.each { |a, b| start_to_goal[a] << b }
    visited = dfs(start_to_goal, START)                            # => {1=>true, 2=>true, 3=>true}
    Edges = Edges.find_all { |from, to, _| visited[from] && visited[to] }
  end

  # ゴールから辿りつけない辺を除外する
  if true
    goal_to_start = Hash.new { |h, k| h[k] = [] }
    Edges.each { |a, b| goal_to_start[b] << a }
    visited = dfs(goal_to_start, GOAL)                             # => {3=>true, 2=>true, 1=>true}
    Edges = Edges.find_all { |from, to, _| visited[from] && visited[to] }
  end
end

d = Hash.new(-Float::INFINITY)  # distance
d[START] = 0

success = catch(:abort) do
  N.times do |i|
    updated = false
    Edges.each do |from, to, score|
      v = d[from] + score
      if v > d[to]
        if i == N.pred
          throw :abort
        end
        d[to] = v
        updated = true
      end
    end
    unless updated
      break
    end
  end
  true
end

p success ? [0, d[GOAL]].max : -1                                  # => 35

事前の準備が大変だが負閉路検出時には即終了して問題ない。

類題

https://zenn.dev/megeton/articles/6f49645f775648

参考

Discussion