ABC243 E問題を直感的に考える
AtCoder Beginner Contest 243 のE問題『Edge Deletion』を解いたので、直感寄りの考え方を書いてみます。
問題の解釈
問題を砕いて表現すると、「無くても困らない道はいくつか」となります。
不要な道とは
まずは極端な例で考えてみます。
下の図のように、A, B, C, D
の4つの地点と、それらを結ぶ道があるとします。
道に書かれている数字は通行コスト、つまり所要時間や通行料金を表します。
ひと目で、 「A-D
直結の道が無くても困らなそうだ」 と感じます。
A-D
の行き来には、A-B-C-D
という経由ルートが使われることでしょう。
ルート | 通行コスト |
---|---|
A-B-C-D 経由ルート |
60 |
A-D 直結ルート |
999999 |
「BからDへ」「AからCへ」など他の組み合わせについても、A-D直結の道を含むルートはとんでもなく高コストになってしまうので、A-D直結の道は不要として良さそうですね。
さて、今回の問題では 経由と直結でコストがぴったり一致する場合も 不要 と扱います。
下のような場合です。
ルート | 通行コスト |
---|---|
A-B-C-D 経由ルート |
60 |
A-D 直結ルート |
60 |
不要な道 の定義
ここまでをまとまめると、
「ある直結の道について、そのコストが 他の経由ルートのコストと同じ あるいは上回る場合、不要」
となります。
では、この言い換えた問題の解き方を考えていきましょう。
最小コスト vs 直結の道
「直結の道を使わない場合の最短ルートは…」と考えると複雑になってしまいます。
そこで、まずはシンプルに 2地点間の最小コスト を求め、そのコストと直結ルートのコストを比べる という方針を考えてみます。
[最小コスト < 直結] のケース
- A~D 間の最小コストを調べたら、60でした。でも、その具体的な経路は覚えていません。どこかを経由するルートだったか、それともA-D直結の道を使ったか…。
- A-D 直結の道があります。そのコストは999999です。
このとき、「他の地点を経由するルートの中に コスト60 で済むものがあるから、直結の道は不要な道」といえますね。
[最小コスト > 直結] のケース
このケースは、存在しません。
もし導き出した最小コストより低コストなルートがあったとしたら、それが最小コストになっているはずです。
[最小コスト = 直結] のケース
このケースは、どうなるでしょうか。
- 最小コストは60でした。でも、その具体的な経路は覚えていません。
- A-D 直結の道があります。そのコストは60です。
もし、他の地点を経由ルートで同じコストのもの(A - ? - D コスト60)があるなら、直結の道は不要といえます。でも、無いなら必要な道です。
では、もし [最小コスト = 直結] のケース
に出会ったら、その際には 全ての経由地点を試す ことにしましょう。
具体的には、 A - ? の最小コスト
+ ? - D の最小コスト
を、全ての地点について調べ、それが最小コストと一致するような地点が存在するならば 直結の道は不要 と確定できますね。
最小コスト早見表を作る
さて、ここまで「最小コストを求めておく」という前提部分がありましたが、それはどのように計算すればよいでしょうか。
ワーシャル–フロイド法 Floyd–Warshall Algorithm というアルゴリズムがあります。
これを用いると、全ての2地点ペアの最小コストを O(N^3)
で求めることができます。
○○法、というものが登場すると「わけわからん」になりがちですが(少なくとも私はそうです)
実はワーシャル–フロイド法はとてもシンプルで、 3重forでテーブルを更新するだけであらゆる2地点間の最小コストを求められてしまう という使い勝手の良いアルゴリズムなので、是非覚えて使ってみましょう。
ワーシャル–フロイド法
ここではイメージのみを紹介します。正確なアルゴリズムや証明については、調べてみてください。
まずは、出発地点と目的地点の表を作ります。
直結の道について、そのコストを書き込みます。
Aまで | Bまで | Cまで | Dまで | |
---|---|---|---|---|
Aから | 10 | 20 | 999999 | |
Bから | 10 | 40 | ∞ | |
Cから | 20 | 40 | 30 | |
Dから | 999999 | ∞ | 30 |
値を埋められなかったところは、仮で∞コストということにしました。
この表は、「現時点でわかっている最小コスト」を表します。
ワーシャル–フロイド法では、A, B, C… 地点を順に経由地点として考えてみて、表をアップデートしていきます。
まずは、A地点を経由する場合を考えます。
- 表によると、BからCまで のコストは40とのことです。
- しかし、同じく表から、BからAまでは10 かつ AからCまでは20 で、合計30。つまり、Aを経由したほうが低コスト ということがわかります。
- この表が表すのは、「現時点でわかっている最小コスト」でした。
なので、先程判明した 30 というコストで上書きします。- ついでに、CからBまで も同じ値で上書きします。
Aまで | Bまで | Cまで | Dまで | |
---|---|---|---|---|
Aから | 10 | 20 | 999999 | |
Bから | 10 | 30 | ∞ | |
Cから | 20 | 30 | 30 | |
Dから | 999999 | ∞ | 30 |
- また、BからD は不明でしたが、BからAまで + AからDまで の値が判明し、これが「現時点でわかっている最小コスト」となります。
Aまで | Bまで | Cまで | Dまで | |
---|---|---|---|---|
Aから | 10 | 20 | 10+999999 | |
Bから | 10 | 30 | ∞ | |
Cから | 20 | 30 | 30 | |
Dから | 999999 | 10+999999 | 30 |
この操作を 全ての地点・全てのセルについて繰り返すと、すべての地点ペアの最小コスト表が完成します。
- A, B, C… 地点を順に経由地点として考えてみて(
O(N)
)- 表全体を更新する (
O(N^2)
)
- 表全体を更新する (
つまり、計算量は O(N^3)
です。
表の更新部分は、3重ループとminのみで実装することができます。
Pythonでの例
for k in range(N):
for i in range(N):
for j in range(N):
table[i][j] = min(table[i][j], table[i][k] + table[k][j])
まとめ
ここまでの方針を繋げると、この問題を解くアルゴリズムが完成します。
- 入力を受け取り、直結の道の情報を保持する。
- ワーシャル–フロイド法で「最小コスト表」を作成する。
- 直結の道それぞれについて、
- 「最小コスト表」を使って
始点 - ? の最小コスト
+? - 終点 の最小コスト
を全ての地点について調べ、最小コストと一致するような地点が存在するなら不要な道と判断
- 「最小コスト表」を使って
- 不要な道の総数を出力する。