Day 048-Q3 — 最大重みクロージャ(Project Selection Problem / Dinic最大流)

2026-06-01 赤色 Master / Phase 8+ ★★★★★★★★★ Maximum Weight Closure / Min-Cut / Network Flow

問題

$N$ 個のプロジェクトと $M$ 個のリソースがある。プロジェクト $i$ を選択すると利益 $p_i$(正値)が得られるが、リソース $j$ の購入コストは $c_j$(正値)。また依存関係があり、「プロジェクト $i$ を選ぶならプロジェクト $k$ も選ばなければならない」という制約がある。選択するプロジェクトの集合 $S$ に対して最大利益を求めよ。

制約

$1 \le N, M \le 500$
$1 \le E \le 2000$(依存辺数)
$1 \le p_i, c_j \le 10^9$
利益 = $\sum p_i - \sum$ (必要リソースコスト)
時間制限: 2秒

入出力例

入力例 1

3 2 2
10 20 5
8 12
1 1
2 2
2 3

出力例 1

15

プロジェクト2(p=20)を選ぶ → リソース2(c=12)が必要 → 利益 20-12=8
プロジェクト1+2(p=10+20=30)→ リソース1+2(c=8+12=20)→ 利益 10
プロジェクト1+2+3(p=35)→ リソース1+2(c=20)→ 利益 15(最大)

概念図: 最大重みクロージャのネットワーク構築

S P1 p=10 P2 p=20 P3 p=5 R1 c=8 R2 c=12 T cap=10 cap=20 cap=5 cap=8 cap=12 最大利益 = 正利益の総和(10+20+5=35) − 最小カット = 35 − 20 = 15

ヒント(段階的開示)

ヒント1: 方向性
「プロジェクト選択問題(Project Selection Problem)」は最大重みクロージャ(Maximum Weight Closure)に帰着でき、さらに最小カット(= 最大流)に帰着できます。
ヒント2: ネットワークの構築
  • ソース $s$ から各プロジェクト $i$ へ容量 $p_i$ の辺
  • 各リソース $j$ からシンク $t$ へ容量 $c_j$ の辺
  • プロジェクト $i$ がリソース $j$ を必要とする → $i \to j$ へ容量 $\infty$ の辺
  • プロジェクト間依存($i$ には $k$ が必要)→ $i \to k$ へ容量 $\infty$ の辺
  • 最大利益 = 正利益の総和 − 最小カット
ヒント3: 最小カットの解釈

カットに含まれる辺は「切り離す辺」を意味する。$s$ 側カットに含まれる辺の容量 = 諦めた利益(プロジェクトを選ばない)+ 購入したリソースのコスト。これを最小化することで最大利益を得る。

total_profit = sum(profits)
min_cut = dinic.max_flow(S, T)
answer = total_profit - min_cut

模範解答 (Python)

import sys
from collections import deque

def solve():
    data = sys.stdin.read().split()
    idx = 0
    N, M, E = int(data[idx]), int(data[idx+1]), int(data[idx+2]); idx += 3
    profits = [int(data[idx+i]) for i in range(N)]; idx += N
    costs = [int(data[idx+i]) for i in range(M)]; idx += M

    S = 0
    T = N + M + 1
    num_nodes = T + 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, 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 > 0 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, iter_):
            if v == t:
                return f
            while iter_[v] < len(self.graph[v]):
                e = self.graph[v][iter_[v]]
                to, cap, rev = e
                if cap > 0 and self.level[v] < self.level[to]:
                    d = self.dfs(to, t, min(f, cap), iter_)
                    if d > 0:
                        e[1] -= d
                        self.graph[to][rev][1] += d
                        return d
                iter_[v] += 1
            return 0

        def max_flow(self, s, t):
            flow = 0
            INF = float('inf')
            while self.bfs(s, t):
                iter_ = [0] * self.n
                while True:
                    f = self.dfs(s, t, INF, iter_)
                    if f == 0:
                        break
                    flow += f
            return flow

    mf = MaxFlow(num_nodes)
    INF = 10**18
    total_profit = 0

    for i in range(N):
        mf.add_edge(S, i+1, profits[i])
        total_profit += profits[i]

    for j in range(M):
        mf.add_edge(N+j+1, T, costs[j])

    for _ in range(E):
        a, b = int(data[idx])-1, int(data[idx+1])-1; idx += 2
        mf.add_edge(a+1, b+1, INF)

    min_cut = mf.max_flow(S, T)
    print(total_profit - min_cut)

solve()

Step-by-Step 解説

1最大重みクロージャへの帰着
「クロージャ」とは、選んだ頂点集合から出る辺の行き先も必ず選ばれている集合。プロジェクト選択問題はこの構造を持つ。
2ネットワーク構築の設計
正利益をソース側(s→プロジェクト)、コストをシンク側(リソース→t)に配置。依存関係は容量 ∞ の辺で表現する。
3最小カットの解釈
s 側のカット辺 = 諦めた利益(プロジェクトの利益を失う)。t 側のカット辺 = 購入するリソースのコスト。最小カットはこれらの和を最小化する。
4Dinic 法で最大流を計算
Dinic 法の計算量は $O(V^2 E)$。本問では N, M, E が小さいので十分高速。

計算量

ノード数: $O(N + M)$
辺数: $O(N + M + E)$
Dinic 最大流: $O(V^2 E) = O((N+M)^2 (N+M+E))$
全体: 本問の制約で十分高速

よくあるミス

ミス原因正しい書き方
依存辺の容量を有限にするカットに含まれてしまう容量 INF(十分大きい数)に設定
正利益の総和を計算しない答えが求まらないtotal_profit - min_cut が答え
逆辺の容量管理ミス最大流の実装バグadd_edge で必ず逆辺(容量0)も追加
コスト辺の向きを逆にするカットの向きが逆リソース → T(シンク方向)

次のステップ

  • 発展問題: 最大密度部分グラフ(Goldberg アルゴリズム、二分探索 + 最大流)
  • 関連: Day042 Q4(最大密度部分グラフ)の復習
  • 応用: Project Selection Problem の多次元拡張、OR最適化

自己評価