この記事でやること
- Dinic法の仕組みの概略解説
- 一般のグラフに対するDinic法の計算量と、実用上の高速化が期待できる基本的なポイントの考察
- 二部グラフの最大マッチング問題でのDinic法の計算量の考察
はじめに(茶番)
AtCoder Beginner Contest 285 にて、二部グラフのマッチングに関する問題が出題されました。
自力では解法が思いつけなかったので(精進不足)解説を開いてみると、どうやら「2」と書かれた頂点を全て含むようなマッチングが存在するか?という問題に帰着されるらしい。なるほど確かに。
あとは最小流量制限付きのフローが存在するかを判定すればいいので、昔書いたDinic法のライブラリを引っ張り出してきて…グラフを構築して…ソイヤ!!
いやいや待て待て、さすがにちょっと適当過ぎたか…。よく考えてみればここらへんとかこんな感じに書けばいいか…あーここは書き直した方がいいなぁ…。こんな感じか?ダメか…こうか?こうすればいいのか?
というとても悲しい事件が起きたので、Dinic法と大真面目に格闘して知ったこと・考えたことを備忘録として残しておきます。
注意点
- 計算量解析については参照元が英語なので、私のガバガバ英訳&補完となっていることをご了承ください。間違っていたらごめんなさい。(どなたか査読して頂けると非常に助かります。)
- 一応Python3.8.3によるソースコードを掲載しておりますが、かなり汚いです。なので、具体的な実装例を参照したい方は他の方のソースコードを漁ってみることをお勧めします。
Dinic法の概要
全体像としては、下記の記事が非常にわかりやすいです。
概略
- Ford-Fulkerson法によると、「残余ネットワーク上でパスを見つけて流す」を繰り返せば最大流が求まるので、「いい感じの順番でパスを見つけて流す」ことでループ回数を抑えたい。
- その手順は、
- 残余ネットワーク上でBFSにより各頂点のからの(辺の重みを全て1とした場合での)最短距離を記録する。
- かつ 」を満たす辺のみを見るDFSでパスを見つけ流せるだけ流すことを、パスが見つからなくなるまで繰り返す。
- BFSでパスが見つからなくなるまで1, 2を繰り返す。
という感じです。
また、2のDFSを繰り返すパートでは配列
を管理することで、(BFS基準での同一パート内で)次回以降のDFSで既に探索済みの辺は参照しないようにします。具体的な処理としては、「DFSで戻ってきたとき、かつ頂点 にまだ到達できていないとき」にインクリメントすればよいです。
実装例
(Python3.8.3)
使用例↓
Submission #38339906 - AtCoder Beginner Contest 010
計算量評価
一般のグラフでのDinic法の計算量は となることが知られています:
- はBFS毎に狭義単調増加する(※証明は後述)ので、1, 2のループは高々回しか発生しません。
- BFSは1回あたり
- DFSでは1回あたり、主に下記の2種類の(BFS基準の同一ループ内で)探索されなくなる辺が存在します:
- パスに含まれず、行き止まりに到達する辺
- パス上に含まれる辺のうち、 が最小の辺*1
- よって、BFS基準の同一ループ内でのDFSによる計算量は「全ての辺が探索されなくなるまでの計算量」で評価することにします。
- 前者の辺は一度使用すると により以降は参照されなくなるため、1本あたりの計算量は とみなせます。*2
- 後者の辺は1本あたりの計算量を とみなせます。
- 辺は 本なので、合わせて となります。
- 以上より、全体で
- (※の証明)
- 現在のBFSパートでの残余ネットワークにおいて、その前のBFSパートでのの振る舞いを考えます。
- 現在の残余ネットワークの任意の辺で、が成立します。・・・①
- を満たす辺が存在すると仮定すると、BFSの性質よりその辺は前BFSパートでは存在しない、つまり、その後のDFSパートで逆辺にフローを流したことになります。
- DFS時のルールにより
- となり、仮定に矛盾
- 現在のBFSパートで見つけたパスにおいて、であることと①より、このパスの長さの下限はとわかります。
- また、このパスの中にはを満たす辺が少なくとも1つ含まれます。
- 含まれない、すなわち任意の辺で と仮定すると、これは前DFSパートで見つかるはずの増加パスとなり現在のBFSパートに移っていることに矛盾
- よって、このパスの長さの下限はとなります。
実用上の高速化ポイント
(言語仕様などによる)実装上の最適化ではなく、アルゴリズム的に実用上の高速化が期待できるポイントを挙げます。
BFSは頂点t に到達した時点で打ち切る
dijkstra等の最短路問題でもよく言われるやつです。DFSで参照する辺の条件から、頂点 以降に訪れる頂点は必ず行き止まりとなります。*3
実は頂点s からDFSするより頂点t からDFSした方が速い(らしい?)
からのDFSでは「(BFS基準の同一ループ内で)初めから行き止まりの頂点」にも訪れてしまいますが、 からのDFSの場合はそのような頂点には訪れることがありません。*4元から行き止まりが少ない場合は逆辺を参照する回数が増える分ほんの少し遅くなるかもしれませんが、トータルで見れば概ね実行時間は改善されます。(要検証)
Submission #38178001 - AtCoder Beginner Contest 285
( からDFS, TLE)
Submission #38177995 - AtCoder Beginner Contest 285
( からDFS, AC [2754ms])
二部マッチングにおけるDinic法の計算量
二部グラフにおける最大マッチングは、下図のようなグラフを構築することで最大流問題に帰着できます。
このグラフにおいては、Dinic法の計算量がとなることが知られています。*5正確には、下記の条件を満たすグラフではこの計算量でDinic法が動作します:
- を除くすべての頂点について、流せる流量が高々1。すなわち、「出る辺のの和」と「入る辺のの和」のいずれか一方は高々1。
- この条件を満たすグラフは以下の2つの性質を持ちます:
- 増加パスに流せる流量は明らかに1。*6
- 増加パスにフローを流しても、残余ネットワークは常に条件を満たす。
- 残余ネットワークが条件を満たすとし、新たに増加パスにフローを流したとします。この流量は1です。
- パス上の を除く各頂点について、流れた流量1のフローにより、入る辺の1つが出る辺(逆辺)へと変わり、出る辺の1つが入る辺(逆辺)へと変わります。
- このとき、の収支は変わっていないため、条件を満たしたままとなります。
- この条件を満たすグラフをunit networkと言うらしい…?*7
以下、計算量の証明:
- DFSパートで パスを見つけた際、先述の条件よりパスに含まれる辺のうち少なくとも本のは1なので、それらは全て以降のDFSで探索されなくなります。
- 言い換えると、DFSパートで見つかる パスの総距離は高々 となります。つまり、DFSパート1回あたりの計算量は(行き止まりの分も合わせて)となります。
- 更に、BFSパートは 回しか発生しないことが示せます:
- 初めて が以上となったときのBFSパートを考えます。
- の狭義単調増加性より、それまでにBFSパートは回しか発生しません。
- このBFSパートに至るまでに流した流量を、最終的に得られる最大フローを とします。このとき、現在の残余ネットワークにおける最大流問題の解、すなわち追加で流せる流量は となります。
- 更に、先述のグラフの性質から の点素なパスが 本あると言い換えられます。
- これらパスの長さは なので、パスに使用する頂点数に関する不等式 が得られます。
- ここで、 より
- ゆえに、BFSパート1回につき少なくともフローを1は流せることからこれ以降のBFSパートは 回しか発生ないとわかります。
- 以上を合わせると、BFSパートは回しか発生しません。
- 以上より、全体で。
おわりに
二部マッチングに対するDinic法はで動作するという話は結構色々なところで見かけましたが、その証明が書かれた文献は全然見つからなかったのでかなり苦労しました。誰か書いておいてくださいよー。
ちなみにDinic法の特殊なグラフでの計算量ってなんか色々あるみたいですね。疲れたので調べるつもりはありませんが…(今のところは)
参考文献
https://people.orie.cornell.edu/dpw/orie633/LectureNotes/lecture9.pdf
Maximum flow - Dinic's algorithm - Algorithms for Competitive Programming
*1:フローを流すことでこの辺が探索されなくなる代わりに逆辺が生じますが、が反転しているためDFSには影響しません。
*2:行き止まりになる前に「パス上に含まれる辺のうち、 が最小でない辺」として複数回使われる可能性はありますが、これは後者の辺の計算量に含まれます。
*3:BFSの性質上、頂点 以降に訪れる任意の頂点の は 以上であるため。
*4:一度以上DFSすることにより新たに生じる行き止まりには到達する可能性があります。
*5:とみなしてと書く場合も多いです。
*6:厳密には が の辺で接続しているという例外はありますが、計算量への影響は明らかにないので無視します。
*7:参考文献[4] ではそう呼ばれていましたが、広く使われている表現なのかはわかりません。