🚧 This website is under construction. 🚧
AtCoder
蟻本
3-5

3-5 水を流して問題を解く "ネットワークフロー"

最大流

>>> import graphviz
>>> dot = graphviz.Digraph(engine="neato")
>>> dot.node("s", pos="-1,1.5!")
>>> dot.node("1", pos="1,3!")
>>> dot.node("2", pos="1.5,0!")
>>> dot.node("3", pos="3,2!")
>>> dot.node("t", pos="4,.5!")
>>> dot.attr(rankdir="LR")
>>> dot.edge("s", "1", "10Mbps")
>>> dot.edge("s", "2", "2Mbps")
>>> dot.edge("1", "2", "6Mbps")
>>> dot.edge("1", "3", "6Mbps")
>>> dot.edge("2", "t", "5Mbps")
>>> dot.edge("3", "2", "3Mbps")
>>> dot.edge("3", "t", "8Mbps")
>>> dot.view()

maximum flow

各辺に対してデータを通信する量を f(e)f(e)、最大の通信可能量を c(e)c(e) とする。このとき以下が成り立つ。

  • 通信量の制約: 0f(e)c(e)0\leqq f(e) \leqq c(e)
  • 入ってくる量と出ていく量は等しい: vV{s,t}v\in V-\{s,t\} に対して、eδ(v)f(e)=eδ+(v)f(e)\sum_{e\in\delta_{-}(v)}f(e)=\sum_{e\in\delta_{+}(v)}f(e)

以上の条件の下、s から出るデータ量 eδ(s)f(e)(=eδ+(t)f(e))\sum_{e\in\delta_{-}(s)}f(e)(=\sum_{e\in\delta_{+}(t)}f(e)) を最大化する。

用語

  • 最大流: 最大のデータ通信量を達成する ff のこと
  • 辺の容量: cc
  • 辺の流量: ff
  • 始点(source): ss
  • 終点(sink): tt

貪欲的アルゴリズム

  1. f(e)<c(e)f(e)\lt c(e) である ee のみを用いた ss から tt までのパスを見つける
  2. そのようなパスがなければ終了。パスが存在したら、そのパスに沿って目一杯流し、1. へ戻る
  • 貪欲的アルゴリズムによって得られる最適でないフロー
  • 実際に最大流となるフロー
  • 最大流となるフローの流量から最適でないフローの流量を引くと...

改良貪欲法アルゴリズム (Ford-Fulkerson)

  1. f(e)<c(e)f(e)\lt c(e) である ee と、f(e)>0f(e)>0 である ee の逆辺 rev(e)rev(e) のみを用いた ss から tt へのパスを見つける
    1. f(e)=c(e)f(e)=c(e) のとき、元の辺は存在しない
  2. そのようなパスがなければ終了。パスが存在したら、そのパスに沿って目一杯流し、1. へ戻る
  • f(e)<c(e)f(e)\lt c(e) である ee と、f(e)>0f(e)>0 である ee の逆辺 rev(e)rev(e) のみからなるグラフを残余グラフという
  • 残余グラフ上での sts-t パスを増加パスという
# Ford-Fulkerson法
 
...

O(FE)\mathcal{O}(|F||E|)

最小カット

用語

  • カット:ある頂点集合 SVS\subseteq V に対して、頂点集合 SS の頂点から、SS に含まれない頂点へ出ていく辺の集合のこと。カット (S,VS)(S,V\setminus S) のように表現される
  • カットの容量:カットの各辺の容量の和

以下 s,ts,t が与えられたとして、

  • sts-t カット: sS,tVSs\in S,t\in V\setminus S となるようにカットすること・またそのようなカットのこと

ss を含み、tt を含まない頂点集合を全通り考えた時、カット容量が最も小さくなるカットを s,ts,t の最小カットという。

💡

sts-t カットを除去すると、ss から tt へのパスは存在しなくなる

>>> import graphviz
>>> dot = graphviz.Digraph(engine="fdp")
>>> dot.node("s", pos="-1,1.5!", color="lightgray", style="filled")
>>> dot.node("1", pos="1,3!")
>>> dot.node("2", pos="1.5,0!")
>>> dot.node("3", pos="3,2!")
>>> dot.node("t", pos="4,.5!", color="lightgray", style="filled")
>>> with dot.subgraph(name="cluster0") as sub_dot:
...     sub_dot.attr(style="rounded,dashed", label="S")
...     sub_dot.edge("s", "2", "2")
...     sub_dot.edge("s", "1", "10")
...     sub_dot.edge("1", "2", "6")
...
>>> with dot.subgraph(name="cluster1") as sub_dot:
...     sub_dot.attr(style="rounded,dashed", label="V\\\S")
...     sub_dot.edge("3", "t", "8")
...
>>> dot.edge('1', '3', '6', color='red', constraint='false')
>>> dot.edge('3', '2', '3', constraint='false')
>>> dot.edge('2', 't', '5', color='red', constraint='false')
>>> dot.view()

s-t cut

最小カット問題

ネットワークに対して、ss から tt へのパスが存在しなくなるために除去しなければならない辺の容量の和の最小値を求める問題

>>> import graphviz
>>> dot = graphviz.Digraph(engine="fdp")
>>> dot.node("s", pos="-1,1.5!", color="lightgray", style="filled")
>>> dot.node("1", pos="1,3!")
>>> dot.node("2", pos="1.5,0!")
>>> dot.node("3", pos="3,2!")
>>> dot.node("t", pos="4,.5!", color="lightgray", style="filled")
>>> with dot.subgraph(name="cluster0") as sub_dot:
...     sub_dot.attr(style="rounded,dashed", label="S")
...     sub_dot.edge("s", "2", "2")
...     sub_dot.edge("s", "1", "10")
...     sub_dot.edge("1", "2", "6")
...
>>> with dot.subgraph(name="cluster1") as sub_dot:
...     sub_dot.attr(style="rounded,dashed", label="V\\\S")
...     sub_dot.edge("3", "t", "8")
...
>>> dot.edge('1', '3', '6', color='red', constraint='false')
>>> dot.edge('3', '2', '3', constraint='false')
>>> dot.edge('2', 't', '5', color='red', constraint='false')
>>> dot.view()

s-t cut

  • カット ({s,1,2},{3,t})(\{s,1,2\},\{3,t\}) が最小カットで、このときのカット容量は 6+5=116+5=11
💡

VSV\setminus{S} から SS への辺はカット (S,VS)(S,V\setminus{S}) に含まれないことに注意

最小カット

2 頂点 s,tVs,t\in V が指定された容量付き有向グラフ G=(V,E,c)G=(V,E,c) において、

c_=min{c(S,VS)  sS,tVS}c^{\_}=\min\{c(S,V\setminus S)\space|\space s\in S,t\in V\setminus S\}

を達成する (S_,VS)(S^{\_},V\setminus S^{*})s,ts,t の最小カットという。

弱双対性定理

2 頂点 s,tVs,t\in V が指定された容量付き有向グラフ G=(V,E,c)G=(V,E,c) において、ss から tt への最大フローの総流量 FF^{*} は以下の式を満たす:

F_c_F^{\_}\le c^{\_}

証明
F=e(S,VS)f(e)e(VS,S)f(e)e(S,VS)f(e)=cF^{*}= \sum_{e\in(S^{*},V\setminus S^{*})}f^{*}(e)- \sum_{e\in(V\setminus S^{*},S^{*})}f^{*}(e)\le \sum_{e\in(S^{*},V\setminus S^{*})}f^{*}(e)=c^{*}

弱双対性定理の系

2 頂点 s,tVs,t\in V が指定された容量付き有向グラフ G=(V,E,c)G=(V,E,c) において、総流量が FFss から tt へのフロー FF' と、sS,tVSs\in S,t\in V\setminus S を満たす頂点集合 SS

F=c(S,VS)F=c(S,V\setminus S)

を満たすとき、FF'ss から tt への最大フローであり、(S,VS)(S,V\setminus S)s,ts,t の最小カットである。

証明
弱双対性定理と最小カットの定義から、FFcc(S,VS)F\le F^{*}\le c^{*}\le c(S,V\setminus S)F=c(S,VS)F=c(S,V\setminus S) となるとき、左式の全ての不等号において等号が成立する。

フロー増加路定理

2 頂点 s,tVs,t\in V が指定された容量付き有向グラフ G=(V,E,e)G=(V,E,e) において、ss から tt へのフロー FF を考えるとき、以下の命題は等価である:

(A)フロー F に対する残余グラフがフロー増加路を含まない(B)フロー F は s から t への最大フローである\begin{aligned} (A)& \text{フロー }F'\text{ に対する残余グラフがフロー増加路を含まない}\\ (B)& \text{フロー }F'\text{ は }s\text{ から }t\text{ への最大フローである} \end{aligned}

証明
(B)->(A):
対偶「フロー FF' に対する残余グラフがフロー増加路を含むならフロー FF'ss から tt への最大フローでない」を示す。これは残余グラフとフロー増加路の定義より明らか。
(A)->(B):
FF' に対する残余グラフにおいて、ss から到達可能な頂点の集合を XX とする。uX,vVXu\in X,v\in V\setminus X とすると、辺 (v,u)(v,u) が残余グラフによって作成された辺である場合、 辺 (u,v)(u,v) において、f(u,v)=e(u,v)f(u,v)=e(u,v) を満たす1。辺 (v,u)(v,u)GG に含まれる場合 f(v,u)=0f(v,u)=0 となる2。以上の議論より、 F=c(X,VX)F=c(X,V\setminus X) が得られ3、弱双対性定理の系より FF' が最大フローであることが示された。

尚 (A)->(B)の証明により、得られるカットが最小カットであることも示される。

強双対性定理(フロー増加路定理の系)

2 頂点 s,tVs,t\in V が指定された容量付き有向グラフ G=(V,E,c)G=(V,E,c) において、ss から tt への最大フローの総流量 FF^{*} は以下の式を満たす:

F=cF^{*}=c^{*}

証明
最大フロー FF^{*} に対して、フロー増加路定理より、FF^{*} に対する残余グラフはフロー増加路を含まない。よって F=c(X,VX)F^{*}=c(X,V\setminus X) であり、弱双対性定理の系から (X,VX)(X,V\setminus X)s,ts,t の最小カット。以上により F=c(X,VX)=cF^{*}=c(X,V\setminus X)=c^{*} が示された。

二部マッチング

用語

  • マッチング:端点を共有しない辺集合 MM のこと
  • 最大マッチング:要素数最大の MM のこと
    • 最大マッチングのサイズが 2M=V2|M|=|V| を満たすとき、グラフは完全マッチングを持つという
  • 二部マッチング:二部グラフにおけるマッチングのこと
>>> import graphviz
>>> dot = graphviz.Graph(engine="neato")
>>> dot.node("u1", pos="0,3!")
>>> dot.node("u2", pos="0,2!")
>>> dot.node("u3", pos="0,1!")
>>> dot.node("v1", pos="1.5,3!")
>>> dot.node("v2", pos="1.5,2!")
>>> dot.node("v3", pos="1.5,1!")
>>> dot.edge("u1", "v1", color="red")
>>> dot.edge("u1", "v2")
>>> dot.edge("u2", "v1")
>>> dot.edge("u2", "v3", color="red")
>>> dot.edge("u3", "v2", color="red")
>>> dot.view()  # 最大マッチングの例: M={(u1, v1), (u2, v3), (u3, v2)}

maximum matcing

グラフの辺 eeUU から VV にへの方向に向き付け、容量を 11 とする。始点と終点を表す 2 つの頂点 s,ts,t を加え、ss から全ての頂点 uUu\in U に向かって容量 11 の辺を、すべての頂点 vVv\in V に向かって容量 11 の辺を張る。

こうして作られたグラフ GG における最大 sts-t フローの流量がもとの二部グラフ GG における最大マッチングのサイズと等しくなり、UVU-V 間の流量が正な辺集合が最大マッチングとなる。

>>> import graphviz
>>> dot = graphviz.Digraph(engine="neato")
>>> dot.node("s", pos="-1,2!", color="lightgray", style="filled")
>>> dot.node("u1", pos="0,3!")
>>> dot.node("u2", pos="0,2!")
>>> dot.node("u3", pos="0,1!")
>>> dot.node("v1", pos="1.5,3!")
>>> dot.node("v2", pos="1.5,2!")
>>> dot.node("v3", pos="1.5,1!")
>>> dot.node("t", pos="2.5,2!", color="lightgray", style="filled")
>>> dot.edge("s", "u1")
>>> dot.edge("s", "u2")
>>> dot.edge("s", "u3")
>>> dot.edge("u1", "v1")
>>> dot.edge("u1", "v2")
>>> dot.edge("u2", "v1")
>>> dot.edge("u2", "v3")
>>> dot.edge("u3", "v2")
>>> dot.edge("v1", "t")
>>> dot.edge("v2", "t")
>>> dot.edge("v3", "t")
>>> dot.view()

Application to Bipartite Matching

# 仕事の割り当て
 
...

一般マッチング

  • Edmonds のアルゴリズム
  • Tutte (タット) 行列を用いた方法

無向グラフ G=(V,E)G=(V,E) を勝手に向き付けて得られる有向グラフを G=(V,E)G'=(V,E') とする。辺 eEe\in E' に対して変数 xex_{e} を用意し、次のように定めた V×VV\times V 行列 T=(tu,v)T=(t_{u,v}) を tutte 行列という。

tu,v={x(u,v)((u,v)E)x(v,u)((v,uE))0(otherwise)t_{u,v}=\begin{cases} x_{(u,v)}((u,v)\in E') \\ -x_{(v,u)}((v,u\in E')) \\ 0 (otherwise) \end{cases}

このとき、GGが完全マッチングを持たない <=> 行列式 det(T)det(T) が恒等的に 00

マッチング・辺カバー・安定集合・点カバー

  • マッチング:辺集合 MEM\subseteq E で、互いに端点を共有しない
  • 辺カバー:辺集合 FEF\subseteq E で、GG のどの頂点も少なくとも 1 つの FF に含まれる辺に接続している
  • 安定集合:点集合 SVS\subseteq V で、SS のどの 2 点も GG において隣接していない
  • 点カバー:点集合 SVS\subseteq V で、GG のどの辺も少なくとも 1 つの SS に含まれる頂点に接続している
  1. 孤立点のないグラフに対し、|最大マッチング| + |最小辺カバー| = |VV|
  2. |最大安定集合| + |最小点カバー| = |VV|
  3. (二部グラフにおいて) |最大マッチング| = |最小点カバー|

最小費用流

  • 流量の最大値ではなく、ある流量 FF を流した時のコストの最小値
  • 残余グラフにおける最短路に基づいて流す
  • 残余グラフにおける逆辺は、コストが負とみなせることから、ベルマンフォード法を用いる

ポテンシャルを導入することで、負辺を解消する事が可能。

ポテンシャル:各頂点 vv に対する重み h(v)h(v) のこと

  • ポテンシャルの初期値:00
  • ポテンシャルの更新:新しいポテンシャル == 古いポテンシャル - 計算した最短路長

http://dopal.cs.uec.ac.jp/okamotoy/lect/2013/opt/handout13.pdf (opens in a new tab)


Footnotes

  1. f(u,v)=e(u,v)f(u,v)=e(u,v) : 0<f(u,v)<e(u,v)0\lt f(u,v)\lt e(u,v) となる場合、辺 (u,v)(u,v) が存在することになるが、これはフロー増加路を含むことになる。

  2. f(v,u)=0f(v,u)=0 : f(v,u)>0f(v,u)>0 となる場合、残余グラフにおいて辺 (u,v)(u,v) が出来るため。同じくフロー増加路を含むことになる。

  3. F=c(X,VX)F=c(X,V\setminus X) : F=f(X,VX)f(VX,X)=f(X,VX)0=f(X,VX)=c(X,VX)F=f(X,V\setminus X)-f(V\setminus X,X)=f(X,V\setminus X)-0=f(X,V\setminus X)=c(X,V\setminus X)

    • f(v,u)=0f(v,u)=0 より、f(VX,X)=0f(V\setminus X,X)=0
    • カットの定義より f(X,VX)=c(X,VX)f(X,V\setminus X)=c(X,V\setminus X)