【Coel.学习笔记】网络流的二分图匹配
最大流问题的一般思路
- 对于一个问题 ,建流网络 。
- 证明原问题的可行解 与流网络的可行流 一一对应,即可行流转化为可行解,可行解又可转化为可行流。
- 那么求最大可行解就转化成了最大流问题。
最小割的思路大同小异,也是让可行解与割一一对应。
二分图例题
网络流 24 题:飞行员配对方案问题
一共有 个飞行员,其中有 个外籍飞行员和 个英国飞行员,外籍飞行员从 到 编号,英国飞行员从 到 编号。 对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
解析:这是一个二分图最大匹配问题。匈牙利算法的时间复杂度为 ,实际上可以通过本题;但 Dinic 求解二分图匹配的时间复杂度为 ,效率更高。
由于每个飞行员只能使用一次,我们可以给每个外籍飞行员与源点连容量为 1 的边,再给每个英国飞行员与汇点连容量为 1 的边;接下来,给外籍飞行员与英国飞行员连边,完成建图。
现在思考一下是否满足“一一对应”的关系。设所有选择边的流量为 1,那么我们通过可行解构造出了一组可行流,显然可行流满足流量守恒、容量限制,所以是正确的;反过来,所有整数值可行流在本问题中是所有可行解,因此这个建图方式可以用于求解。
代码如下:
// Problem: P2756 飞行员配对方案问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2756
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 6e3 + 10, inf = 1e9;
int m, n, s, t;
int head[maxn], nxt[maxn], to[maxn], c[maxn], cnt;
int d[maxn], cur[maxn];
void add(int u, int v, int w) {
nxt[cnt] = head[u], to[cnt] = v, c[cnt] = w, head[u] = cnt++;
nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}
bool bfs() {
queue<int> Q;
memset(d, -1, sizeof(d));
Q.push(s), d[s] = 0, cur[s] = head[s];
while (!Q.empty()) {
int u = Q.front();
Q.pop();
for (int i = head[u]; i != -1; i = nxt[i]) {
int v = to[i];
if (d[v] == -1 && c[i]) {
d[v] = d[u] + 1;
cur[v] = head[v];
if (v == t)
return true;
Q.push(v);
}
}
}
return false;
}
int find(int u, int limit) {
if (u == t)
return limit;
int flow = 0;
for (int i = cur[u]; i != -1 && flow < limit; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if (d[v] == d[u] + 1 && c[i]) {
int tem = find(v, min(c[i], limit - flow));
if (!tem) d[v] = -1;
c[i] -= tem, c[i ^ 1] += tem;
flow += tem;
}
}
return flow;
}
int dinic() {
int ans = 0, flow;
while (bfs())
while ((flow = find(s, inf)))
ans += flow;
return ans;
}
int main(void) {
cin >> m >> n;
s = 0, t = n + 1;
memset(head, -1, sizeof(head));
for (int i = 1; i <= m; i++)
add(s, i, 1);
for (int i = m + 1; i <= n; i++)
add(i, t, 1);
int u, v;
while (cin >> u >> v, u != -1 && v != -1)
add(u, v, 1);
cout << dinic() << '\n';
for (int i = 0; i < cnt; i += 2) {
if (to[i] > m && to[i] <= n && !c[i])
cout << to[i ^ 1] << ' ' << to[i] << '\n';
}
return 0;
}
网络流 24 题:圆桌问题
有来自 个不同单位的代表参加一次国际会议。第 个单位派出了 个代表。会议的餐厅共有 张餐桌,第 张餐桌可容纳 个代表就餐。
为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。请给出一个满足要求的代表就餐方案。
解析:同样采用二分图的思路,给 个单位与 个圆桌分别连边,容量全为 1,此时这题转化为二分图多重匹配问题。
继续利用上面最大流的思路,建立源点与汇点,其中源点与每个单位连边,容量为单位代表人数;汇点与每张餐桌连边,容量为餐桌可容纳的人数。
再次证明建图的正确性。首先,对于原问题任意一个可行解,给所有选择边赋流量为 1,可以发现对于每个餐桌和单位代表,都满足容量限制和流量守恒,因此可行解对应可行流;反之,对于任何一个整数可行流都有一个对应的可行解,这一点与上一题是相同的。
代码如下:
// Problem: P3254 圆桌问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3254
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 1e5 + 10, inf = 1e9;
int n, m, s, t, tot;
int head[maxn], nxt[maxn], to[maxn], c[maxn], cnt;
int d[maxn], cur[maxn];
void add(int u,int v, int w) {
nxt[cnt] = head[u], to[cnt] = v, c[cnt] = w, head[u] = cnt++;
nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}
bool bfs() {
queue<int> Q;
memset(d, -1, sizeof(d));
Q.push(s), d[s] = 0, cur[s] = head[s];
while (!Q.empty()) {
int u = Q.front();
Q.pop();
for (int i = head[u]; i != -1; i = nxt[i]) {
int v = to[i];
if (d[v] == -1 && c[i]) {
d[v] = d[u] + 1;
cur[v] = head[v];
if (v == t)
return true;
Q.push(v);
}
}
}
return false;
}
int find(int u, int limit) {
if (u == t)
return limit;
int flow = 0;
for (int i = cur[u]; i != -1 && flow < limit; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if (d[v] == d[u] + 1 && c[i]) {
int tem = find(v, min(c[i], limit - flow));
if (!tem) d[v] = -1;
c[i] -= tem, c[i ^ 1] += tem, flow += tem;
}
}
return flow;
}
int Dinic() {
int ans = 0, flow;
while (bfs())
while ((flow = find(s, inf)))
ans += flow;
return ans;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> m >> n;
s = 0, t = m + n + 1;
memset(head, -1, sizeof(head));
for (int i = 1, w; i <= m; i++) {
cin >> w;
add(s, i, w);
tot += w;
}
for (int i = 1, w; i <= n; i++) {
cin >> w;
add(m + i, t, w);
}
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
add(i, m + j, 1);
if (Dinic() != tot)
cout << 0;
else {
cout << 1 << '\n';
for (int i = 1; i <= m; i++) {
for (int j = head[i]; j != -1; j = nxt[j])
if (to[j] > m && to[j] <= m + n && !c[j])
cout << to[j] - m << ' ';
cout << '\n';
}
}
return 0;
}
在这两道题里我们可以发现,网络最大流问题的难点通常不在于算法本身,而是认真分析问题与流网络之间的关系,构造合理的流网络,从而得到解。
本文作者:Coel's Blog
本文链接:https://www.cnblogs.com/Coel-Flannette/p/16465603.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。