【Coel.学习笔记】网络流的二分图匹配

最大流问题的一般思路

  1. 对于一个问题 \(P\),建流网络 \(G\)
  2. 证明原问题的可行解 \(s\) 与流网络的可行流 \(f\) 一一对应,即可行流转化为可行解,可行解又可转化为可行流。
  3. 那么求最大可行解就转化成了最大流问题。
    最小割的思路大同小异,也是让可行解与割一一对应。

二分图例题

网络流 24 题:飞行员配对方案问题

洛谷传送门

一共有 \(n\) 个飞行员,其中有 \(m\) 个外籍飞行员和 \((n - m)\) 个英国飞行员,外籍飞行员从 \(1\)\(m\) 编号英国飞行员从 \(m + 1\)\(n\) 编号。 对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。

解析:这是一个二分图最大匹配问题。匈牙利算法的时间复杂度为 \(O(nm)\),实际上可以通过本题;但 Dinic 求解二分图匹配的时间复杂度为 \(O(m \sqrt n)\),效率更高。

由于每个飞行员只能使用一次,我们可以给每个外籍飞行员与源点连容量为 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 题:圆桌问题

洛谷传送门

有来自 \(m\) 个不同单位的代表参加一次国际会议。第 \(i\) 个单位派出了 \(r_i\) 个代表。会议的餐厅共有 \(n\) 张餐桌,第 \(i\) 张餐桌可容纳 \(c_i\) 个代表就餐。

为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。请给出一个满足要求的代表就餐方案。

解析:同样采用二分图的思路,给 \(m\) 个单位与 \(n\) 个圆桌分别连边,容量全为 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;
}

在这两道题里我们可以发现,网络最大流问题的难点通常不在于算法本身,而是认真分析问题与流网络之间的关系,构造合理的流网络,从而得到解。

posted @ 2022-07-11 10:33  秋泉こあい  阅读(160)  评论(0编辑  收藏  举报