最大流解决二分图匹配问题

文章目录

    • 零、前言
    • 一、二分图匹配转化为网络流模型
      • 1.1建模步骤
      • 1.2整数值最大流和二分图匹配的关系
      • 1.3代码实现
    • 二、OJ练习
      • P2756 飞行员配对方案问题
      • P3254 圆桌问题


零、前言

阅读本文前,需具备以下知识:

二分图及染色法判定-CSDN博客

二分图最大匹配——匈牙利算法详解-CSDN博客

最大流—EK算法,流网络,残留网络,定理证明,详细代码-CSDN博客

最大流-Dinic算法,原理详解,四大优化,详细代码-CSDN博客


一、二分图匹配转化为网络流模型

最大流解决二分图匹配问题_第1张图片

1.1建模步骤

  1. 二分图G中创建虚拟源点s,虚拟汇点t,s和左部点边,t和右部点连边
  2. 得到新图G‘,新图G’中所有边容量设为1
  3. 在G‘中寻找整数值最大流f,原二分图G中所有有流量的边即为最大匹配
    最大流解决二分图匹配问题_第2张图片

1.2整数值最大流和二分图匹配的关系

对于流网络中的最大流,不一定为整数,也可以是浮点数,所以我们有两个问题:

整数值可行流是否是二分图中一个匹配,二分图中的一个匹配是否对应一个整数值可行流?

对于可行流而言,由于所有边的容量上限都为1,所以每个左部点最多流经1点流量,也最多将这1点流量流向一个右部点,即每个左部点最多和一个右部点建立流量,换句话说,任取两条有流量的边,必然没有公共点,所以可行流是一个匹配。

那么对于一个匹配而言,我们匹配边赋予1点流量,再建立虚拟源点和虚拟汇点,我们发现新图除源汇点外满足容量守恒,斜对称和容量限制,所以是一个可行流。

于是建立了整数可行流和二分图最大匹配之间的双射关系。

为什么一定能找到一个整数值最大流

由于建立的流网络中只用到了整数值,我们求解最大流的算法也都只用到了整数值,所以最大流一定是整数流

1.3代码实现

以P3386 【模板】二分图最大匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)为模板
时间复杂度:(On m^0.5)

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N = 1010, M = (50000 + N) << 1, inf = 1e9;
struct edge
{
    int v, c, nxt;
} edges[M];
int n, m, e, s, t, head[N], d[N], cur[N], idx = 0;
inline void addedge(int u, int v, int c)
{
    edges[idx] = {v, c, head[u]}, head[u] = idx++;
}
inline void add(int u, int v, int c)
{
    addedge(u, v, c), addedge(v, u, 0);
}
int dfs(int u, int limit)
{
    if (u == t)
        return limit;
    int res = 0;
    for (int i = cur[u]; ~i && limit; i = edges[i].nxt)
    {
        cur[u] = i;
        int v = edges[i].v;
        if (d[v] == d[u] + 1 && edges[i].c)
        {
            int incf = dfs(v, min(limit, edges[i].c));
            if (!incf)
                d[v] = 0;
            limit -= incf, res += incf, edges[i].c -= incf, edges[i ^ 1].c += incf;
        }
    }
    return res;
}
bool bfs()
{
    memset(d, 0, sizeof(d));
    queue q;
    q.emplace(s), d[s] = 1;
    while (q.size())
    {
        int u = q.front();
        q.pop();
        for (int i = head[u]; ~i; i = edges[i].nxt)
        {
            int v = edges[i].v;
            if (!d[v] && edges[i].c)
            {
                d[v] = d[u] + 1, q.emplace(v);
                if (v == t)
                    return true;
            }
        }
    }
    return false;
}
int dinic()
{
    int res = 0;
    while (bfs())
        memcpy(cur, head, sizeof(head)), res += dfs(s, inf);
    return res;
}
int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0), memset(head, -1, sizeof(head));
    // freopen("in.txt", "r", stdin);
    int a, b, c;
    cin >> n >> m >> e, s = 0, t = n + m + 1;

    for (int i = 0; i < e; i++)
        cin >> a >> b, add(a, b + n, 1);
    for (int i = 1; i <= n; i++)
        add(s, i, 1);
    for (int i = 1; i <= m; i++)
        add(i + n, t, 1);
    cout << dinic();
    return 0;
}

二、OJ练习

P2756 飞行员配对方案问题

P2756 飞行员配对方案问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

很显然是一个二分图最大匹配问题,建图跑板子即可,然后题目还要求我们输出匹配边的两个节点,我们遍历匹配边,反向边和正向边的邻接点即为两个匹配点

F1 Dinic

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define sc scanf
#define int long long
#define N 105
#define M 10010
const int MOD = 10000007;
const int inf = 0x3f3f3f3f3f3f3f3f;

int n, m, s, t, idx = 0;
int d[N], cur[N], head[N]; // 深度,当前边,前向星头
struct edge
{
    int v, c, nxt;
} edges[M];

inline void addedge(int u, int v, int c)
{
    edges[idx] = {v, c, head[u]};
    head[u] = idx++;
}

bool bfs() // 多路增广
{
    memset(d, 0, sizeof(d));
    queue q;
    q.emplace(s), d[s] = 1;
    while (q.size())
    {
        int u = q.front();
        q.pop();
        for (int i = head[u]; ~i; i = edges[i].nxt)
        {
            int v = edges[i].v;
            if (!d[v] && edges[i].c)
            {
                d[v] = d[u] + 1;
                q.emplace(v);
                if (v == t)
                    return true;
            }
        }
    }
    return false;
}

int dfs(int u, int limit)
{
    if (u == t)
        return limit;
    int ret = 0;
    for (int i = cur[u]; ~i && limit > 0; i = edges[i].nxt)
    {
        cur[u] = i;
        int v = edges[i].v;
        if (d[v] == d[u] + 1 && edges[i].c)
        {
            int incf = dfs(v, min(limit, edges[i].c));
            if (!incf)
                d[v] = 0;
            edges[i].c -= incf, edges[i ^ 1].c += incf, ret += incf, limit -= incf;
        }
    }
    return ret;
}

int dinic()
{
    int ret = 0;
    while (bfs())
        memcpy(cur, head, sizeof(head)), ret += dfs(s, inf);
    return ret;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen("in.txt", "r", stdin);
    memset(head, -1, sizeof(head));
    cin >> m >> n;
    int a, b;
    s = 0, t = n + 1;
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= m; i++)
        addedge(s, i, 1), addedge(i, s, 0);
    for (int i = m + 1; i <= n; i++)
        addedge(i, t, 1), addedge(t, i, 0);
    while (1)
    {
        cin >> a >> b;
        if (a == -1 && b == -1)
            break;
        addedge(a, b, 1), addedge(b, a, 0);
    }
    cout << dinic() << '\n';
    for (int i = 0; i < idx; i += 2)
        if (edges[i].v > m && edges[i].v <= n && !edges[i].c)
            cout << edges[i ^ 1].v << " " << edges[i].v << '\n';
}

F2 匈牙利求最大匹配

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define sc scanf
#define int long long
#define N 105
#define M 10010
const int MOD = 10000007;
const int inf = 0x3f3f3f3f3f3f3f3f;

int n, m, idx, ans = 0;
int match[N]{0}, head[N];
bool vis[N];
struct edge
{
    int v, nxt;
} edges[M << 1];

inline void addedge(int u, int v)
{
    edges[idx] = {v, head[u]};
    head[u] = idx++;
}

bool dfs(int u)
{
    for (int i = head[u]; ~i; i = edges[i].nxt)
    {
        int v = edges[i].v;
        if (vis[v])
            continue;
        vis[v] = 1;
        if (!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen("in.txt", "r", stdin);
    memset(head, -1, sizeof(head));
    cin >> m >> n;
    int a, b;
    memset(head, -1, sizeof(head));
    while (1)
    {
        cin >> a >> b;
        if (a == -1 && b == -1)
            break;
        addedge(a, b);
    }
    for (int i = 1; i <= m; i++)
        memset(vis, 0, sizeof(vis)), ans += dfs(i);
    cout << ans << '\n';
    for (int i = m + 1; i <= n; i++)
        if (match[i])
            cout << match[i] << " " << i << '\n';
}

P3254 圆桌问题

P3254 圆桌问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

仍然是二分图最大匹配,不过这里是多重匹配,处理多重匹配我们的策略是拆点,当然可以用匈牙利来做,这里我们直接用Dinic,体会一下如何将问题抽象为网络流问题。

这里建图相较于匈牙利解法就很爽了,每个左部点右部点最大连边数就是它们跟源点汇点边的容量,然后左右部点之间互相连容量为1的边,然后跑板子即可。

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int N = 430, M = (150 * 270 + N) * 2;
const int inf = 1e9;
int n, m, s, t, idx = 0, cur[N], head[N], d[N];
struct edge
{
    int v, c, nxt;
} edges[M];
inline void addedge(int u, int v, int c)
{
    edges[idx] = {v, c, head[u]};
    head[u] = idx++;
}
inline void add(int u, int v, int c)
{
    addedge(u, v, c), addedge(v, u, 0);
}
bool bfs()
{
    memset(d, 0, sizeof(d));
    queue q;
    q.emplace(s), d[s] = 1;
    while (q.size())
    {
        int u = q.front();
        q.pop();
        for (int i = head[u]; ~i; i = edges[i].nxt)
        {
            int v = edges[i].v;
            if (!d[v] && edges[i].c)
            {
                d[v] = d[u] + 1;
                q.emplace(v);
                if (v == t)
                    return true;
            }
        }
    }
    return false;
}
int dfs(int u, int limit)
{
    if (u == t)
        return limit;
    int ret = 0;
    for (int i = cur[u]; ~i && limit; i = edges[i].nxt)
    {
        cur[u] = i;
        int v = edges[i].v;
        if (d[v] == d[u] + 1 && edges[i].c)
        {
            int incf = dfs(v, min(limit, edges[i].c));
            if (!incf)
                d[v] = 0;
            ret += incf, limit -= incf, edges[i].c -= incf, edges[i ^ 1].c += incf;
        }
    }
    return ret;
}
int dinic()
{
    int ret = 0;
    while (bfs())
        memcpy(cur, head, sizeof(head)), ret += dfs(s, inf);
    return ret;
}
int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    freopen("in.txt", "r", stdin);
    cin >> m >> n;
    s = 0, t = m + n + 1;
    memset(head, -1, sizeof(head));
    int tot = 0;
    for (int i = 1; i <= m; i++)
    {
        int r;
        cin >> r, add(s, i, r), tot += r;
    }
    for (int i = 1; i <= n; i++)
    {
        int c;
        cin >> c, add(m + i, t, c);
    }
    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; j = edges[j].nxt)
                if (edges[j].v > m && edges[j].v <= m + n && !edges[j].c)
                    cout << edges[j].v - m << ' ';
            cout << '\n';
        }
    }
}

你可能感兴趣的:(数据结构与算法,开发语言,c++,数据结构,网络流,二分图)