なんとな~くしあわせ?の日記

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

AtCoder - AtCoder Beginner Contest 012 (バスと避けられない運命) を解いてみた

前回の競プロ!

ダイクストラ法を学んでようやくスタート地点に立てました。

ダイクストラ法については以下のエントリを参照。

nantonaku-shiawase.hatenablog.com

解法

Rubyによる解答 TLE

アルゴリズムは正しそうですが11個のテストケースでTLEしてしまいました。

Submission #1725365 - AtCoder Beginner Contest 012 | AtCoder

f:id:panzer-jagdironscrap1:20171031213136p:plain

D言語による解答 AC!

しかしそんなことでは諦めない。もちろん俺らは抵抗するで?拳で*1

Submission #1728053 - AtCoder Beginner Contest 012 | AtCoder

f:id:panzer-jagdironscrap1:20171031213451p:plain

AOJ - ALDS1_12_B (ダイクストラ法) を解いてみた

AOJ - ALDS1_12は、重み付きのグラフアルゴリズムです。

最短経路 ダイクストラ法 | アルゴリズムとデータ構造 | Aizu Online Judge

この問題が解けるとAtCoderの以下の問題も解けます

abc012.contest.atcoder.jp

問題設定

最短経路問題というのは、2つのノード間を結ぶ経路の中で重みが最小の経路を求める最適化問題のことらしい。

詳しいところはWikipediaをよめばよろし → 最短経路問題 - Wikipedia

一筆書きは求められていません。一度訪問したノードからは再出発できます。

最短経路問題の分類とアルゴリズムの使いどころ

今回はWikipediaの記述が役に立っています。最短経路問題は条件ごとに複数のアルゴリズムが適用されるみたいです。

Wikipediaの記述を整理すると…


[最短経路問題]
   │
   ├── [ 2頂点対最短経路問題 ] (単一始点と同じロジックで対応できる)
   │ 
   ├── [ 単一始点最短経路問題(SSSP) ]          
   │             ├── [ 辺の重みなし有向グラフ ]      → 幅優先探索
   │             ├── [ 辺の重みが非負値の有向グラフ ]   → ダイクストラ法
   │             └── [ 辺の重みが任意の実数の有向グラフ ] → ベルマン-フォード法
   │
   └── [ 全点対最短経路問題 ]
                 └── ワーシャル-フロイド法


チラチラ数学の論文っぽい参照が散見されますが、競プロのために論文は読まんでいいはず…。とにもかくにも、最短経路を求めるときに辺の重みがマイナスじゃなけりゃダイクストラ法を使えばいいのです。あと、もう一つダイクストラ法には制約があって、有向グラフ(辺が一方通行)のものしか適用できないようです。

AOJの問題の注意

  • プリム法は無向グラフが対象でしたが、ダイクストラ法のグラフは有向グラフです
  • プリム法は1-indexedなグラフでしたが、なぜかダイクストラ法のほうは0-indexedです*1

グラフをGraphvizでビジュアル化する

文章だとわからんので、グラフをGraphvizでビジュアル化してみました。

AOJ ALDS1_12_Bのグラフを図示

f:id:panzer-jagdironscrap1:20171029165936p:plain

0から出発したときに、各ノードに到達する時間(重み)を求めます。正解は以下

  • 左がノード、右がそのノードまでの0からの距離
0 0
1 2
2 2
3 1
4 3

ノードは5個ですが、すでに目検では最短経路の探索は難しくなっています。

Wikipediaの記述にあるようにこのアルゴリズムはカーナビなどの地図上の2点の最短経路を求めるために使われていたりするのでした。

アルゴリズム

疑似コード

ダイクストラ法 - Wikipedia

Wikipediaの疑似コードを見ると、ソースの内容が前回のプリム法と似ていることに気付くかもしれません。プリム法ではノードの距離の比較の際、隣接行列の値と現在の距離だけを比較していましたが、ダイクストラ法は隣接行列の値と現在もっているノードの距離と現在の距離を比較します。

再びヨークカレッジ ペンシルバニア校/York College of Pennsylvaniaのレジェメを見る。

Lecture 22: Single Source Shortest Paths - Dijkstra's Algorithm

DIJKSTRA(G,w,s)
   INITIALIZE-SINGLE-SOURCE(G,s)
   S = ∅
   Q = G.V
   while Q ≠ ∅
      u = EXTRACT-MIN(Q)
      S = S ∪ {u}
      for each vertex v ∈ G.Adj[u]
         RELAX(u,v,w) 
         # ↑ ここの関数は単に分割されとるだけ
         # 一番下のRELAX関数をここにコピペするとプリム法とほぼ同じ

INITIALIZE-SINGLE-SOURCE(G,s)
   for each vertex v ∈ G.V
      v.d = ∞
      v.pi = NIL
   s.d = 0

RELAX(u,v,w)
   if v.d > u.d + w(u,v)
      v.d = u.d + w(u,v)
      v.pi = u

*1:統一してくれ…

AOJ - ALDS1_12_A (プリム法) を解いてみた

AOJ - ALDS1_12は、重み付きのグラフアルゴリズムです。

最小全域木| アルゴリズムとデータ構造 | Aizu Online Judge

問題設定

最小全域木というのは、

  • 重みがある経路前提
  • 閉路がない

グラフにおいて、すべてのノードを一度回るのに最も少ないコストを見つけるための方法です。詳しいところはWikipediaをよめばよろし → 全域木 - Wikipedia

すべてのノードを回るとは言っても、一筆書きは求められていません。一度訪問したノードからは再出発できます。

グラフをGraphvizでビジュアル化する

文章だとわからんので、グラフをGraphvizでビジュアル化してみました。

AOJ ALDS1_12_Aのグラフを図示

最小全域木って要は下みたいな地図をすべて訪問する最小時間を求める!みたいな問題なんですね、そう考えるとめっちゃ有用性感じるでしょ?

例えば丸の中に数字があるところが営業先で、丸と線をつなぐ数値が移動時間だと考えるとイメージが具体的になる。 営業マンはできるだけ多くの得意先を最小の時間で回る経路を知りたい。スタート地点は①。

f:id:panzer-jagdironscrap1:20171029134300p:plain

実際、この経路を目でたどるのは簡単*1

f:id:panzer-jagdironscrap1:20171029134732p:plain

アルゴリズム

疑似コード

Wikipediaの疑似コードは数学寄りで分かりにくかったです。

プリム法 - Wikipedia

それに引き換え、再び参照したサイト from ヨークカレッジ ペンシルバニア校/York College of Pennsylvaniaはいい感じです。また疑似コードを拝借します。

PRIM(G,w,r)
   for each u ∈ G.V
      u.key = ∞
      u.pi = NIL
   r.key = 0
   Q = G.V
   while Q ≠ ∅
      u = EXTRACT-MIN(Q)
      for each v ∈ G.Adj[u]
         if v ∈ Q and w(u,v) < v.key
            v.pi = u
            v.key = w(u,v)

Lecture 20: Minimum Spanning Trees - Prim's Algorithm


ただ、どちらも求めた最小距離をどうやって合計するかが書かれていない。

コードの工夫など

  • INFの表現

到達できないノード、もしくは未発見のノードへの距離は無限とあらわすのが一般的なようで、とりあえず大きい数に置いておいた

  • ノードが含む要素
    • :u → ノードの番号(1〜)
    • :c → 訪問状況を3値(=3色)で
    • :d → ノード間の距離の記録
    • :p → 一つ前に探索したノードの記録
INF  = 999999999
Node = Struct.new(:u, :d, :c, :p)

*1:経路のノードが膨大になると無理になってくるが