最大流問題を解くアルゴリズムについて, ちょろっと書いておきます.
要約すると, 言いたいことは
の二つです. 半分自分用のメモみたいなもんです.
Dinic についてちゃんと知っている人が読むメリットは多分無いです.
ツッコミなどあれば, ぜひお願いします.
蟻本のページ番号が書いてある時は, 第二版のものです.
最大流問題を知らない人は, 蟻本(p. 188から少し)を読んで下さい.
今回は, グラフ 上で, から への最大流を求めたいとします.
また, , とします. (こんな準備要るのかな)
最大流を解くアルゴリズムの一つに, Dinic 法というヤツがあります.
Dinic 法については, とりあえず, 蟻本のコラム(p. 194)に書いてある事を把握しましょう.
ここでは, "1ステップ" というのは, 一度 bfs をしてから, もう一度 bfs をするまでを指す事にします.
各ステップの dfs が で終わる事は, そんなに自明ではないです.
実際,
なので, 単純に掛け算すると になります.
参考文献1を眺めると, になる事を見るには, 辺が使われなくなるタイミングに注目すればよいようです.
注意2までを読んでからの方が解りやすい気がしますが, 大雑把言うと, 多分こういう感じです.
元々は, と 両方から bfs をして, 真に関係ある頂点と辺だけを取り出します.
フローを流すときも, それによって使えなくなった辺や頂点はその場で取り除くようにしています.
普段よく使っているのは, どうやら Cherkassky version に近いようです.(参考文献1)
bfs では, からの距離で, 各頂点をラベリングしています.
dfs で見るのは, 始点のラベルが終点のラベルより小さい辺のみです.
この辺のみを見ると, 例えば次のようなグラフになります.
いくつかの注意を, このグラフ上で, から に だけ流れ, このステップが終わるまでに, dfs がどう動くかと共に見ていきます.
蟻本(p. 194) に書いてある
無駄な辺を何度も調べない
というのは, dfs内のループが iter[v] をインデックスとして回っている事に対応しています.
これはかなり重要で, これがないと, 入力によってはかなり遅くなります.
Dinic 法のライブラリを持っている人は, ちゃんとそうなっているか確かめましょう. (そうしていない人もそれなりに多いようです.)
これを怠った場合, dfs は次の順序で頂点を見る事になります.
一回目の dfs では,
により,
なる増加パスを見つけ, これに沿ってに 1 流します.
二回目の dfs では,
により,
なる増加パスを見つけ, これに沿ってに 1 流します.
次の dfs は失敗し, これ以上 から へは流れない事が解り, このステップは終了します.
一方, ちゃんと実装した場合, 一回目の dfs は同じですが, 二回目の dfs では,
により,
なる増加パスを見つけます.
前回の dfs の続きから始めるというイメージです.
これをしないと, オーダーレベルで遅くなるはず.
一回目の dfs で
なる増加パスを見つけた時, 使えなくなる辺は のみです.
従って, 次の dfs でも, まで探索される事がわかり, ここから継続して dfs を行うようにすればよい事が解ります.
少し工夫すれば, 各ステップの dfs が一回で済むように実装する事が出来, この時の探索順序は
のようになります.
上の探索順を見ていると, 明らかに に辿りつけない を探索するのは無駄だなぁという感じがします.
これを解消するには, dfs を から, 逆グラフに沿ってするように変更すればよいです.
この時の探索順序は,
となります.
bfs と dfs を逆から探索するようにすればよいので, からの距離でラベリングし, から dfs を行なってもよいです.
bfs を順グラフ, dfs を逆グラフで行うほうが, 実測でよい結果が出ましたが, これは, グラフの持ち方の問題で, 逆グラフでの bfs で辺をイテレートする際のキャッシュヒット率が低い事によるものと思われます. (dfs の方はどっちにしろ低くなる.)
Dinic 法は, 上で書いたように, (ちゃんと実装すれば) です.
しかし, 経験的にはもっと速く感じます.
速い. 体感計算量などという言葉を作りたくなるくらい. 体感計算量, 定数倍が重めな . (個人の感想です)
グラフの構築にかかる時間が無視できない感じ.
参考文献1 によれば,
で, こういうグラフなら速い保証があってよいです.
当然なんだけれども, 普段の Dinic からは信じられない遅さ.
最悪ケースについては, 次の二つ(参考文献の2と3)が参考になります.
一つ目は, その名の通りの論文で, ステップかかる素疎グラフ(割と自明なケース)を挙げています.
素疎なので, ワーストケースってほどワーストじゃない気もしますが, ステップ数が 回かかるケースです. (平均的なケースではもっと滅茶苦茶少ない)
二つ目は, TopCoder の Tutorial ですが, 真ん中あたりにある密グラフがヤバいケースです.
ここ に置いておきます.
実験用の部分が多いです.
蟻本と Spaghetti Source にかなり影響をうけています.
割とどうしてくれても良いですが, WAやTLE喰らっても知りません.
理解してから, (他の人のソース見ながらでも)自分で実装する事をおすすめします.
どういうデータが欲しいかに従って, 自分で計測して下さい.
参考として, 自分の環境で, 自分が書いたコードが 秒程度かかるものを挙げます.
但し, グラフの構築にかかる時間は無視しています.
特にランダムは, ケース数少ないし, 信用できるようなデータではないと考えた方がよいです.
ケース | 頂点数 | 辺数 |
---|---|---|
ランダムを80ケース(合計) | 5000 | 1000000 |
ランダムを40ケース(合計) | 50000 | 1000000 |
ランダムを13ケース(合計) | 500000 | 1000000 |
密なランダムを5ケース(合計) | 5000 | 約 |
完全グラフを15ケース(合計) | 2000 | |
ワーストケース1つ目 | 7000 | 13997 |
ワーストケース2つ目 | 628 | 33491 |
ワーストケースはマジ死ぬ.
Dinic 法は使い所を間違えると辛い事になる.
例えば, 次のような条件が揃うとヤバめ.
こういう時は, Goldberg-Tarjan とか, Push/Relabel 系のアルゴリズムを使った方がよさげ.
逆に,
の場合は, 割と制約大きくても, Dinic のワーストケースを突くのが難しく, 大丈夫そう.
特に, 前に言った, 辺容量が全て同じ場合や, 二部グラフの場合など.
1. は no title の一番上から読めます.
2. は Worst case behavior of the Dinic algorithm から読めます.
3. は Topcoder.
7742015/10/15 23:28「Dinic 法と, その注意点」の注意2の部分について
探索順序は
S, a, b, a, e, a, S, b, e, b, f, b, S, c, g, T, g, c, h, T, h, c, S
ではないですか?最初に g に来たときに c に戻るのはなぜでしょうか.
私が理解していない/勘違いしていたら済みません.
Mi_Sawa2015/10/15 23:39(最初の b が d ですが)そのとおりですね. ご指摘ありがとうございます, 修正します.