問題
$N$ 頂点 $M$ 辺の無向グラフで、部分集合 $S \subseteq V$($S \neq \emptyset$)の密度を $\rho(S) = |E(S)| / |S|$ と定義する。密度の最大値 $\rho^*$ を有理数で出力せよ。
制約
$1 \le N \le 500$
$1 \le M \le 2000$
時間制限: 3sec / メモリ: 256MB
入出力例
入力例 1
5 6
1 2
1 3
2 3
2 4
3 5
4 5
出力例 1
1/1
三角形 {1,2,3} は 3辺/3頂点 = 1.0。
概念図: 密度最大化のフロー構成
ヒント(段階的開示)
ヒント1: 方向性
密度 $\rho(S) = |E(S)|/|S|$ を直接最適化するのは難しい。Goldberg のアルゴリズムは「$\rho(S) > g$ となる $S$ が存在するか?」を最大流で判定し、二分探索で $\rho^*$ を求める。
ヒント2: アプローチ
- 判定 threshold $g$ に対してフローネットワークを構成。
- $s \to v$: 容量 $\deg(v)/2 + g$、$v \to t$: 容量 $\deg(v)/2$、辺 $(u,v)$: $u \leftrightarrow v$ 各容量 1。
- 最大流が総 $s$-出力容量 $\sum(\deg(v)/2 + g)$ に等しい iff $\rho^* \le g$。
- 二分探索で $g$ を絞り込み、最後に Fraction で有理数化。
ヒント3: 実装骨格
def check(g):
# g が密度上限か判定
mf = MaxFlow(N + 2)
s, t = N, N + 1
total_cap = 0
for u, v in edges:
mf.add_edge(u, v, 1)
mf.add_edge(v, u, 1)
for v in range(N):
sv_cap = deg[v] / 2 + g
mf.add_edge(s, v, sv_cap)
mf.add_edge(v, t, deg[v] / 2)
total_cap += sv_cap
return mf.max_flow(s, t) >= total_cap - 1e-9
lo, hi = 0.0, M
for _ in range(100):
mid = (lo + hi) / 2
if check(mid): hi = mid
else: lo = mid
模範解答 (Python)
import sys
from collections import deque
from fractions import Fraction
def solve():
data = sys.stdin.read().split()
idx = 0
N, M = int(data[idx]), int(data[idx+1]); idx += 2
edges = []
deg = [0] * N
for _ in range(M):
u, v = int(data[idx])-1, int(data[idx+1])-1; idx += 2
edges.append((u, v))
deg[u] += 1; deg[v] += 1
class MaxFlow:
def __init__(self, n):
self.n = n
self.graph = [[] for _ in range(n)]
def add_edge(self, u, v, cap):
self.graph[u].append([v, cap, len(self.graph[v])])
self.graph[v].append([u, 0.0, len(self.graph[u])-1])
def bfs(self, s, t):
self.level = [-1]*self.n; self.level[s] = 0
q = deque([s])
while q:
v = q.popleft()
for to, cap, _ in self.graph[v]:
if cap > 1e-9 and self.level[to] < 0:
self.level[to] = self.level[v]+1; q.append(to)
return self.level[t] >= 0
def dfs(self, v, t, f, it):
if v == t: return f
while it[v] < len(self.graph[v]):
to, cap, rev = self.graph[v][it[v]]
if cap > 1e-9 and self.level[v] < self.level[to]:
d = self.dfs(to, t, min(f, cap), it)
if d > 1e-12:
self.graph[v][it[v]][1] -= d
self.graph[to][rev][1] += d
return d
it[v] += 1
return 0.0
def max_flow(self, s, t):
flow = 0.0
while self.bfs(s, t):
it = [0]*self.n
while True:
f = self.dfs(s, t, float('inf'), it)
if f < 1e-12: break
flow += f
return flow
def check(g):
mf = MaxFlow(N + 2)
s, t = N, N + 1
total = 0.0
for u, v in edges:
mf.add_edge(u, v, 1.0); mf.add_edge(v, u, 1.0)
for v in range(N):
cap = deg[v] / 2.0 + g
mf.add_edge(s, v, cap); mf.add_edge(v, t, deg[v] / 2.0)
total += cap
return mf.max_flow(s, t) >= total - 1e-6
lo, hi = 0.0, float(M)
for _ in range(100):
mid = (lo + hi) / 2
if check(mid): hi = mid
else: lo = mid
frac = Fraction(lo).limit_denominator(N * (N-1) // 2 + 1)
print(f"{frac.numerator}/{frac.denominator}")
solve()
Step-by-Step 解説
1密度最大化の困難さ
$\rho(S) = |E(S)|/|S|$ は比率の最大化であり、整数計画問題。直接解くのは NP 困難に見えるが、グラフの密度最大化は多項式時間で解ける特殊構造を持つ。
$\rho(S) = |E(S)|/|S|$ は比率の最大化であり、整数計画問題。直接解くのは NP 困難に見えるが、グラフの密度最大化は多項式時間で解ける特殊構造を持つ。
2Goldberg の判定フロー
「密度 $> g$ の部分集合が存在するか」を s-t 最大流で判定。$s \to v$ 容量 $\deg(v)/2 + g$、$v \to t$ 容量 $\deg(v)/2$、元の辺は両方向に容量 1。最大流 = 総 $s$-容量なら $\rho^* \le g$。
「密度 $> g$ の部分集合が存在するか」を s-t 最大流で判定。$s \to v$ 容量 $\deg(v)/2 + g$、$v \to t$ 容量 $\deg(v)/2$、元の辺は両方向に容量 1。最大流 = 総 $s$-容量なら $\rho^* \le g$。
3二分探索の収束
$g \in [0, M]$ を 100 回二分探索すれば十分な精度($\approx M/2^{100}$)。最後に `Fraction.limit_denominator` で有理数に復元する。
$g \in [0, M]$ を 100 回二分探索すれば十分な精度($\approx M/2^{100}$)。最後に `Fraction.limit_denominator` で有理数に復元する。
4最小カットからの部分集合復元
最適 $g$ でのフロー後、$s$ 側の最小カットを BFS/DFS で探索すると最大密度部分集合 $S^*$ が復元できる。
最適 $g$ でのフロー後、$s$ 側の最小カットを BFS/DFS で探索すると最大密度部分集合 $S^*$ が復元できる。
計算量
各判定(Dinic 法): $O(V^2 E)$
二分探索: $O(\log(M/\varepsilon))$ 回
合計: $O(V^2 E \log(M/\varepsilon))$
$N=500, M=2000$ で実用的に動作
二分探索: $O(\log(M/\varepsilon))$ 回
合計: $O(V^2 E \log(M/\varepsilon))$
$N=500, M=2000$ で実用的に動作
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 整数容量で実数を表現 | Dinic の整数前提 | スケール(×10^6)するか浮動小数点 Dinic |
| 判定の向き(> vs ≥) | 等号のケース | 浮動小数点 EPS を適切に設定 |
| 有理数復元の精度不足 | limit_denominator 上限が小さい | 分母は最大 $N(N-1)/2$ |
次のステップ
- 発展問題: 辺重み付き密度最大化 $|E(S)|_w / |S|$ の最大化
- 類題: CF 786F "Symmetric dataset"
- 応用: ソーシャルネットワーク解析・クラスタリング(最大密度コミュニティ検出)