問題
頂点 $1..N$、有向辺 $M$ 本のネットワークがある。辺 $k$ は $u_k\to v_k$、容量 $c_k$、1 単位あたりのコスト $w_k$(負もあり得る。負のコストは「流すと利益が出る」こと)。各頂点 $v$ に供給量 $b_v$ がある(正なら湧き出し、負なら吸い込み、$\sum b_v=0$)。
各辺の流量 $0\le f_k\le c_k$(整数)を決めて、すべての頂点で
$$(\text{出ていく流量})-(\text{入ってくる流量})=b_v$$
を満たすとき、$\sum_k w_kf_k$ の最小値を求めよ。条件を満たす流し方が無ければ infeasible と出力せよ。
入力形式
N M
b_1 b_2 ... b_N
u_1 v_1 c_1 w_1
...
u_M v_M c_M w_M
制約
| # | 条件 |
|---|---|
| 1 | $2\le N\le300$、$1\le M\le3000$ |
| 2 | $\vert b_v\vert \le10^{12}$、$\sum b_v=0$ |
| 3 | $1\le u_k,v_k\le N$、$u_k\ne v_k$(多重辺はあり得る) |
| 4 | $0\le c_k\le10^9$、$\vert w_k\vert \le10^6$ |
| 5 | 入力はすべて整数 |
入出力例
入力例1
3 3
2 0 -2
1 2 2 1
2 3 3 1
3 2 1 -5出力例1
0(1→2 に 2、2→3 に 3、3→2 に 1。2→3→2 の閉路はコスト $1-5=-4$ の負の閉路で、1 単位回すと得。$2+3-5=0$)
入力例2
2 2
0 0
1 2 5 -3
2 1 4 1出力例2
-8(供給は 0 でも、負の閉路 1→2→1 に 4 単位回して $-12+4=-8$)
入力例3
2 1
3 -3
1 2 2 1出力例3
infeasible背景と出典
最小費用流の逐次最短路法(SSP)は、残余グラフに負の閉路が無いこと(=各頂点のポテンシャルで全辺の被約コストを非負にできること)を前提にする。Edmonds–Karp(1972)がポテンシャル付き Dijkstra の形を示した、と私は理解している(原典は本セッションでは未確認)。
負の閉路があってもよい一般の最小費用流(最小費用循環)の解き方は大きく 2 つ。
- 負閉路消去法(Klein 1967): まず実行可能な流れを作り、残余グラフの負の閉路を見つけては流し切る。正しいが遅い
- 負の辺の先行飽和: コストが負の辺 $u\to v$ には最初から容量いっぱい $c$ を流したことにし、$b_u$ から $c$ を引き、$b_v$ に $c$ を足す。残余グラフにはその辺の逆辺 $v\to u$(コスト $-w>0$)だけが残るので、全辺のコストが非負になり、負の閉路も消える。あとは超頂点を足した普通の SSP
本問は 2 を主解、1 を検算に使う。2 の名前は AtCoder Library の解説や日本語の競プロ記事で「負辺を飽和させる」と呼ばれているのを見た記憶がある(特定の出典は未確認)。
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
「負のコストの辺は、流せるだけ流したほうが得」が出発点。もし最適解でその辺に容量より少なく流していたら、それは「流したものを一部押し戻した」と見なせる。押し戻すことを辺の逆向きの流れとして表すと、コストの符号が反転する。
ヒント2(アプローチ)
コスト $w<0$ の辺 $u\to v$(容量 $c$)を次のように置き換える。
| 変更 | 内容 |
|---|---|
| 定数 | 答えに $c\cdot w$ を足しておく |
| 供給 | $b_u\leftarrow b_u-c$、$b_v\leftarrow b_v+c$ |
| 辺 | $v\to u$、容量 $c$、コスト $-w$($>0$)を追加。元の辺は消す |
変換後のどの流れも元の問題の流れと 1 対 1 に対応し(新しい辺の流量 $g$ ↔ 元の辺の流量 $c-g$)、コストは定数 $cw$ だけずれる。
ヒント3(誘導)
超頂点 $S$ から $b_v>0$ の頂点へ容量 $b_v$、$b_v<0$ の頂点から $T$ へ容量 $-b_v$ の辺を張る。全辺が非負コストなので、ポテンシャル 0 から Dijkstra の SSP を回せる。$S$ から出る容量を流し切れなければ infeasible。
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
from heapq import heappush, heappop
def main():
data = sys.stdin.buffer.read().split()
n, m = int(data[0]), int(data[1])
b = [int(x) for x in data[2:2 + n]]
pos = 2 + n
S, T = n, n + 1
V = n + 2
# 残余グラフ: 辺 k の逆辺は k^1
to, cap, cost, head, nxt = [], [], [], [-1] * V, []
def add_edge(u, v, c, w):
to.append(v); cap.append(c); cost.append(w); nxt.append(head[u]); head[u] = len(to) - 1
to.append(u); cap.append(0); cost.append(-w); nxt.append(head[v]); head[v] = len(to) - 1
excess = b[:]
base = 0
for _ in range(m):
u, v, c, w = (int(x) for x in data[pos:pos + 4]); pos += 4
u -= 1; v -= 1
if w >= 0:
add_edge(u, v, c, w)
else:
# 負の辺は先に容量いっぱい流しておく。残余グラフには v→u(コスト -w > 0)だけが残る
base += c * w
excess[u] -= c
excess[v] += c
add_edge(v, u, c, -w)
need = 0
for v in range(n):
if excess[v] > 0:
add_edge(S, v, excess[v], 0)
need += excess[v]
elif excess[v] < 0:
add_edge(v, T, -excess[v], 0)
# 全辺のコストが非負なので、ポテンシャル 0 から Dijkstra で逐次最短路
INF = float('inf')
h = [0] * V
flow = 0
total = 0
while flow < need:
dist = [INF] * V
pe = [-1] * V
dist[S] = 0
pq = [(0, S)]
while pq:
d, u = heappop(pq)
if d > dist[u]:
continue
hu = h[u]
k = head[u]
while k != -1:
if cap[k] > 0:
v = to[k]
nd = d + cost[k] + hu - h[v]
if nd < dist[v]:
dist[v] = nd
pe[v] = k
heappush(pq, (nd, v))
k = nxt[k]
if dist[T] == INF:
print("infeasible")
return
for v in range(V):
if dist[v] < INF:
h[v] += dist[v]
f = need - flow
v = T
while v != S:
k = pe[v]
if cap[k] < f:
f = cap[k]
v = to[k ^ 1]
v = T
while v != S:
k = pe[v]
cap[k] -= f
cap[k ^ 1] += f
v = to[k ^ 1]
flow += f
total += f * (h[T] - h[S])
print(base + total)
main()
計算量: 1 回の増加が Dijkstra の $O((N+M)\log N)$。増加の回数は理論上は流量に比例し得るが、各回でどこかの辺が飽和するので実際は辺数程度。$N=300,M=3000$(負の辺 5 割)で 増加 830 回・1.04 s(Python 3.14.7、M1 Mac、実測)。答えの絶対値は $3000\cdot10^9\cdot10^6=3\times10^{18}$ 以下で 64 ビットに収まる(導出)。
Step-by-Step 解説
元の辺の流量 $f\in[0,c]$ と、新しい逆辺の流量 $g=c-f\in[0,c]$ は 1 対 1。保存則は $u$ で「出る $f$」が「出る $c$ − 入る $g$」に、$v$ で「入る $f$」が「入る $c$ − 出る $g$」に変わるだけなので、$b$ を $\mp c$ ずらせば一致する。コストは $wf=wc+(-w)g$。
変換後の辺はすべてコスト非負。超頂点の辺もコスト 0。だから初期の残余グラフに負の閉路は無く、ポテンシャル 0 が正しい。SSP は「最短路で増やす限り負の閉路が生まれない」性質を保つので、最後まで Dijkstra が使える。
負の閉路があると、Bellman–Ford の距離は収束せず、得られた値をポテンシャルにしても被約コストが負の辺が残る。Dijkstra は確定済みの頂点を更新できないので、最短でない路で増やし、負の閉路にも流さない。入力例 2 で 0(正解 −8)、入力例 1 で 4(正解 0)を出す(実測)。
入力例 2 のように $b$ がすべて 0 でも、負の閉路に流すと得をする。「$S$ から流す量が 0 だから何もしない」は誤り。先行飽和すると $b$ が 0 でなくなり、自動的に正しく流れる。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 変数ごとの全列挙 | 指数 | 検算用のみ | $N\le4,M\le6$、容量 $\le2$ の 300 件の正解 |
| 負閉路消去法(最大流→Bellman–Ford で閉路を消す) | 閉路の数 × $O(NM)$ | 検算用のみ | 正しいが遅い。中規模 200 件と $N=60,M=400$ の 5 件で本解と一致 |
| Bellman–Ford のポテンシャル+Dijkstra | $O(NM)+$SSP | 不採用 | 負の閉路で誤る(小 19/300・中 87/200 誤答) |
| 先行飽和+Dijkstra の SSP(本解) | 増加回数 × $O(M\log N)$ | 採用 | 全照合で一致、$N=300,M=3000$ で 1.04 s |
| 容量スケーリング | $O(M\log U\cdot M\log N)$ | 不採用 | 増加回数が流量に比例する入力で強いが、本セッションで実装・検証していない |
| ネットワーク単体法 | 実用上最速級 | 不採用 | 実装が長い。$N,M$ が $10^5$ なら LEMON などの実装を使う側に回る |
前提が変わったら:
- 負の閉路が無いと保証されるなら、Bellman–Ford(または DAG なら DP)でポテンシャルを作る Day037-Q5 の方法でもよい。先行飽和でも同じく正しい
- $N=1000,M=5000$ で 4.4〜5.1 s、$N=2000,M=10^4$ で 17.8 s(実測)。この規模なら容量スケーリングかネットワーク単体法に切り替える
- 流量の上限を与えて「流量 $F$ の最小費用」なら、$S$ と $T$ をつないで $b$ を作るだけで同じコードになる
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| Bellman–Ford のポテンシャルで Dijkstra | 負の閉路が無いと思い込む | 負の辺を先行飽和(87/200 誤答) |
| 先行飽和で $b$ の符号を逆にする | 「出る−入る=$b$」の向き | $b_u-=c$、$b_v+=c$ |
| 定数 $c\cdot w$ を足し忘れる | 変換後の流れのコストだけ出す | base += c * w |
| 供給が全部 0 なら即 0 を出力 | 循環の得を見落とす | 先行飽和すれば自動的に扱える(入力例 2) |
| 容量 0 の負の辺で落ちる | 0 除算などを心配して特別扱い | 容量 0 なら何も変わらない(そのまま) |
次のステップ
- 発展1: 最適な流れを出力し(元の辺の流量 $=c-g$)、各頂点のポテンシャルが双対の最適解(被約コストが非負・相補性)になっていることを確かめる
- 発展2: 容量スケーリングを実装し、$N=1000,M=5000$ で本解と比べる
- 発展3: 「負の閉路があるかどうか」だけを $O(NM)$ で判定し、閉路を 1 つ復元する
検証ログ(本セッションで実行した数値)
- 全列挙との照合(実測): $N\le4$、$M\le6$、容量 $\le2$、コスト $|w|\le5$(半分を負に)のランダム 300 件で本解・負閉路消去法とも不一致 0。うち 134 件が
infeasible - 負閉路消去法との照合(実測): $N\le30$、$M\le80$、容量 $\le10^6$ のランダム 200 件で不一致 0(84 件が
infeasible)。$N=60,M=400$、容量 $\le10^9$ の 5 件も一致(例: −5782574504741494) - 誤った方法(実測): Bellman–Ford+Dijkstra は小 300 件中 19 件、中 200 件中 87 件誤答。入力例 1 で 4、例 2 で 0 を出す
- 実行時間(実測): $N=200,M=2000$ で 0.40〜0.63 s(増加 454〜670 回)、$N=300,M=3000$ で 1.04 s(830 回、$|w|\le10^6$)と 1.14 s(838 回、$|w|\le10^9$)、$N=1000,M=5000$ で 4.44・5.06 s、$N=2000,M=10^4$ で 17.83 s(これが $N\le300$ にした理由)
- 入力例(実測): 例 1
0、例 2-8、例 3infeasible(全列挙でも同じ) - 扱っていない範囲: 容量スケーリング・ネットワーク単体法、双対解の出力、Klein・Edmonds–Karp の原典