ホーム BOJ 11378 - 熱血江湖 4
記事
キャンセル

BOJ 11378 - 熱血江湖 4

問題ページ

問題モデル

N人の社員とM件の仕事があり、各社員が担当できる仕事の一覧が与えられる。各社員は通常最大1件を担当でき、追加割り当ての総数はK以下である。追加割り当ては社員1人につき最大1件なので、各社員の担当数は最大2件となる。各仕事は高々1人にだけ割り当てる。この条件のもとで、割り当てる仕事数を最大化する。

最大フローモデル

頂点はソース、N人の社員、ボーナス頂点、M件の仕事、シンクで構成する。辺を次のように張る。

  • ソース → 社員: 容量1(各社員の通常割り当て)
  • ソース → ボーナス頂点: 容量K(追加割り当ての総上限)
  • ボーナス頂点 → 社員: 容量1(社員ごとの追加割り当ては最大1件)
  • 社員 → 担当可能な仕事: 容量1
  • 仕事 → シンク: 容量1(各仕事は最大1回だけ割り当て)

各社員に入るフローは通常辺とボーナス辺からそれぞれ最大1なので、合計で最大2である。社員に入るボーナスフローも最大1で、ボーナス頂点へ入るフローはK以下に制限される。したがって、社員が担当できる仕事は最大2件、追加割り当ては全体でK件以下となる。ボーナス経路だけからフローを受け取った社員がいても、その割り当ては1件に限られる。有効な割り当ては、各社員の1件目を通常辺、2件目をボーナス経路に通すことでフローとして表せる。逆に、整数フローの社員-仕事辺は各仕事を重複なく割り当て、上記の容量制約を満たす。よって最大フロー値は割り当て可能な仕事数の最大値に等しい。

アルゴリズムと計算量

Dinic法では、残余グラフ上でBFSによりレベルグラフを作り、レベルに沿ってブロッキングフローを送る。これ以上増加できなくなったときのフローが最大フローである。ここでV = N + M + 3、E = A + 2N + M + 1とし、Aは社員-仕事の適格辺数とする。一般のフローネットワークに対するDinic法の時間計算量はO(V^2 E)、空間計算量はO(V + E)である。問題の制約はN, M ≤ 1,000、K ≤ Nである。この実装は問題固有の、より高速な最悪計算量を主張しない。

C++17実装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;

struct Dinic {
    struct Edge {
        int to;
        int rev;
        int cap;
    };

    vector<vector<Edge>> graph;
    vector<int> level;
    vector<int> nextEdge;

    explicit Dinic(int n)
        : graph(n), level(n), nextEdge(n) {}

    void addEdge(int from, int to, int cap) {
        Edge forward{to, static_cast<int>(graph[to].size()), cap};
        Edge reverse{from, static_cast<int>(graph[from].size()), 0};
        graph[from].push_back(forward);
        graph[to].push_back(reverse);
    }

    bool buildLevels(int source, int sink) {
        fill(level.begin(), level.end(), -1);
        queue<int> q;
        level[source] = 0;
        q.push(source);

        while (!q.empty()) {
            int current = q.front();
            q.pop();
            for (const Edge& edge : graph[current]) {
                if (edge.cap > 0 && level[edge.to] == -1) {
                    level[edge.to] = level[current] + 1;
                    q.push(edge.to);
                }
            }
        }
        return level[sink] != -1;
    }

    int sendFlow(int current, int sink, int pushed) {
        if (current == sink || pushed == 0) return pushed;

        for (int& i = nextEdge[current]; i < static_cast<int>(graph[current].size()); ++i) {
            Edge& edge = graph[current][i];
            if (edge.cap == 0 || level[edge.to] != level[current] + 1) continue;

            int sent = sendFlow(edge.to, sink, min(pushed, edge.cap));
            if (sent == 0) continue;

            edge.cap -= sent;
            graph[edge.to][edge.rev].cap += sent;
            return sent;
        }
        return 0;
    }

    int maxFlow(int source, int sink) {
        int total = 0;
        while (buildLevels(source, sink)) {
            fill(nextEdge.begin(), nextEdge.end(), 0);
            while (int sent = sendFlow(source, sink, 1'000'000'000)) {
                total += sent;
            }
        }
        return total;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, k;
    cin >> n >> m >> k;

    const int source = 0;
    const int bonus = n + 1;
    const int firstJob = n + 2;
    const int sink = n + m + 2;
    Dinic dinic(sink + 1);

    for (int worker = 1; worker <= n; ++worker) {
        dinic.addEdge(source, worker, 1);
    }
    dinic.addEdge(source, bonus, k);
    for (int worker = 1; worker <= n; ++worker) {
        dinic.addEdge(bonus, worker, 1);
    }

    for (int worker = 1; worker <= n; ++worker) {
        int count;
        cin >> count;
        while (count--) {
            int job;
            cin >> job;
            dinic.addEdge(worker, firstJob + job - 1, 1);
        }
    }
    for (int job = 0; job < m; ++job) {
        dinic.addEdge(firstJob + job, sink, 1);
    }

    cout << dinic.maxFlow(source, sink) << '\n';
    return 0;
}
この記事は著者により CC BY 4.0 ライセンスで公開されています。