問題
$N$ 頂点 $M$ 辺の有向グラフ。各辺に容量 $c$、コスト $d$、下限流量 $l$。最小費用循環流とそのパス・サイクル分解を求めよ。
制約
$2 \le N \le 100$
$1 \le M \le 500$
$0 \le l \le c \le 100$
入出力例
入力例 1
3 3
1 2 5 2 1
2 3 5 3 0
3 1 5 1 2
出力例 1
Minimum cost: 12
Flow decomposition:
Cycle: 1 -> 2 -> 3 -> 1, flow=2, cost=12
ヒント (段階的開示)
ヒント1: 方向性
下限付き循環流 → 変数変換 $f' = f - l$ で 0 下限化。超源/超汐への辺で需要を満たす。
ヒント2: アプローチ
SPFA + 増加路法で MCF。負コスト辺がある可能性があるため Bellman-Ford 系。
ヒント3: 誘導
フロー分解: DFS でサイクル検出、最小流量分を引いて辺の消費。高々 $|E|$ 個のサイクル/パスに分解。
模範解答 (Python)
import sys
from collections import deque, defaultdict
input = sys.stdin.readline
INF = float('inf')
class MinCostFlow:
def __init__(self, n):
self.n = n
self.graph = [[] for _ in range(n)]
def add_edge(self, u, v, cap, cost):
fwd = len(self.graph[u])
bwd = len(self.graph[v])
self.graph[u].append([v, cap, cost, bwd])
self.graph[v].append([u, 0, -cost, fwd])
return fwd
def min_cost_flow(self, s, t, max_flow=INF):
total_flow = 0
total_cost = 0
while True:
dist = [INF] * self.n
in_queue = [False] * self.n
prev_v = [-1] * self.n
prev_e = [-1] * self.n
dist[s] = 0
q = deque([s]); in_queue[s] = True
while q:
v = q.popleft(); in_queue[v] = False
for i, (to, cap, cost, _) in enumerate(self.graph[v]):
if cap > 0 and dist[v] + cost < dist[to]:
dist[to] = dist[v] + cost
prev_v[to] = v; prev_e[to] = i
if not in_queue[to]:
q.append(to); in_queue[to] = True
if dist[t] == INF: break
d = max_flow - total_flow
v = t
while v != s:
d = min(d, self.graph[prev_v[v]][prev_e[v]][1])
v = prev_v[v]
if d == 0: break
v = t
while v != s:
self.graph[prev_v[v]][prev_e[v]][1] -= d
rev = self.graph[prev_v[v]][prev_e[v]][3]
self.graph[v][rev][1] += d
v = prev_v[v]
total_flow += d
total_cost += d * dist[t]
if total_flow >= max_flow: break
return total_flow, total_cost
def main():
N, M = map(int, input().split())
edges = []
for _ in range(M):
u, v, c, d, l = map(int, input().split())
edges.append((u - 1, v - 1, c, d, l))
S, T = N, N + 1
mcf = MinCostFlow(N + 2)
b = [0] * N
edge_indices = []
for idx, (u, v, c, d, l) in enumerate(edges):
ei = mcf.add_edge(u, v, c - l, d)
edge_indices.append((u, ei))
b[v] += l; b[u] -= l
required_flow = 0
for v in range(N):
if b[v] > 0:
mcf.add_edge(S, v, b[v], 0); required_flow += b[v]
elif b[v] < 0:
mcf.add_edge(v, T, -b[v], 0)
flow, cost = mcf.min_cost_flow(S, T, required_flow)
if flow < required_flow:
print("No feasible circulation exists."); return
actual_flow = []
base_cost = sum(l * d for _, _, _, d, l in edges)
for idx, (u, v, c, d, l) in enumerate(edges):
eu, ei = edge_indices[idx]
used_cap = (c - l) - mcf.graph[eu][ei][1]
actual_flow.append(l + used_cap)
total_cost = cost + base_cost
print(f"Minimum cost: {total_cost}")
flow_graph = defaultdict(lambda: defaultdict(int))
for idx, (u, v, c, d, l) in enumerate(edges):
if actual_flow[idx] > 0:
flow_graph[u][v] += actual_flow[idx]
decomposed = []
def find_cycle():
for start in range(N):
if not any(flow_graph[start].values()): continue
path = [start]; visited = {start: 0}; v = start
while True:
moved = False
for nxt, f in list(flow_graph[v].items()):
if f > 0:
if nxt in visited:
cs = visited[nxt]
cycle = path[cs:]
cf = min(flow_graph[cycle[i]][cycle[(i+1) % len(cycle)]] for i in range(len(cycle)))
for i in range(len(cycle)):
flow_graph[cycle[i]][cycle[(i+1) % len(cycle)]] -= cf
return cycle, cf
path.append(nxt); visited[nxt] = len(path) - 1; v = nxt; moved = True; break
if not moved: break
return None, 0
for _ in range(M * 2):
cycle, flow_val = find_cycle()
if cycle is None: break
if flow_val > 0:
cycle_cost = 0
for i in range(len(cycle)):
u, v = cycle[i], cycle[(i+1) % len(cycle)]
for idx2, (eu, ev, ec, ed, el) in enumerate(edges):
if eu == u and ev == v:
cycle_cost += ed * flow_val; break
decomposed.append(("Cycle", cycle + [cycle[0]], flow_val, cycle_cost))
print("Flow decomposition:")
if not decomposed:
print("(no flow)")
else:
for kind, path, f, c in decomposed:
path_str = " -> ".join(str(v + 1) for v in path)
print(f"{kind}: {path_str}, flow={f}, cost={c}")
main()
Step-by-Step 解説
1下限付き変換
$f' = f - l$ で容量 $c - l$。$b[v]$ で需要超過量を計算、超源/超汐で吸収。
$f' = f - l$ で容量 $c - l$。$b[v]$ で需要超過量を計算、超源/超汐で吸収。
2SPFA で MCF
負コスト対応の Bellman-Ford ベース。実用上は速い。
負コスト対応の Bellman-Ford ベース。実用上は速い。
3フロー分解定理
整数フロー $f$ は高々 $|E|$ 本のパス・サイクルに分解可能(各ステップで少なくとも1辺消費)。
整数フロー $f$ は高々 $|E|$ 本のパス・サイクルに分解可能(各ステップで少なくとも1辺消費)。
4DFS でサイクル抽出
サイクル発見 → 最小流量分を引いて辺を消費。
サイクル発見 → 最小流量分を引いて辺を消費。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| $b(v)$ の符号反転 | 入超過 / 出超過混同 | 入が +、出が − |
| 復元時 $l$ を忘れる | 変換後フロー | actual = l + used_cap |
| BFS で負コスト | BFS は負に対応しない | SPFA を使う |
次のステップ
- 下限付き最大流
- プロジェクトスケジューリング (CPM + 下限)