网络流(二)最大流之二分图匹配

最大流之二分图匹配

二分图匹配模型匈牙利算法的复杂度为 O ( n m ) O(nm) O(nm) 最大流(Dinic) 复杂度为 O ( m n ) O(m\sqrt{n}) O(mn )

二分图匹配问题见图方式较为固定,设两个集合男孩集合A 和 女孩集合B 进行配对,首先从源点向女生集合(男生具体哪个集合连源点根据题目所给的边决定)中的所有点连一条边,从另外一个集合中所有点向汇点连一条边,边权均为1跑最大流即为二分图匹配数。

飞行员配对方案

根据题意可得,外籍飞行员和英国本土飞行员两两成为一组,典型的二分图匹配问题,输出的答案为二分图匹配数,直接上最大流集合。

建图大致如下:

网络流(二)最大流之二分图匹配_第1张图片

证明:

​ (1)、流量守恒:对于每个点都有两种可能,一是成功找到相匹配的飞行员,在这对匹配中的两个点流入流出的流量均为1,满足流量守恒。二是没有找到相匹配的飞行员,流入流出的流量均为0,仍然满足流量守恒。

​ (2)、容量限制:可以看出流量只有可能是0或者1,虽然在dinic算法中的流量不一定是整型,但是流量的这两种取值均满足熔炼限制,且整型流的最大流也一定是整个流网络的最大流。

参考代码:

#include
#include
#include
#include
#define int long long 
using namespace std;
const int N = 1e3 + 10, M = 1e4 + 10, inf = 1e18;
int n, m, S, T;
int h[N], e[M], ne[M], f[M], idx;
int q[N], d[N], cur[N];
void add(int a, int b, int c){
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs(){
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S;
    d[S] = 0;
    cur[S] = h[S];
    while(hh <= tt){
        int t = q[hh ++];
        for(int i = h[t] ; i != -1; i = ne[i]){
            int ver = e[i];
            if(d[ver] == -1 && f[i]){
                cur[ver] = h[ver];
                d[ver] = d[t] + 1;
                if(ver == T){
                    return true;
                }
                q[++ tt] = ver;
            }
        }
    }
    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 = ne[i]){
        cur[u] = i;
        int ver = e[i];
        if(d[ver] == d[u] + 1 && f[i]){
            int t = find(ver, min(f[i], limit - flow));
            if(!t){
                d[ver] = -1;
            }
            f[i] -= t;
            f[i ^ 1] += t;
            flow += t;
        }
    }
    return flow;
}
int dinic(){
    int r = 0, flow;
    while(bfs()){
        while(flow = find(S, inf)){
            r += flow;
        }
    }
    return r;
}
signed main(){
    cin >> m >> n;
    memset(h, -1, sizeof(h));
    S = 0;
    T = n + 1;
    // 总共n个飞行员 1 ~ m 号为外籍, m + 1 ~ n 号为英国飞行员。
    for(int i = 1; i <= m ; i ++){
        add(S, i, 1);
    }
    for(int i = m + 1; i <= n ; i ++){
        add(i, T, 1);
    }
    int a, b;
    while(cin >> a >> b){
        if(a == -1 && b == -1){
            break;
        }
        add(a, b, 1);
    }
    // 输出结果, 即统计有哪些边的流量满流并且出点是本土飞行员。
    printf("%lld\n", dinic());
    for(int i = 0; i < idx; i += 2){
        if(e[i] > m && e[i] <= n && !f[i]){
            printf("%lld %lld\n", e[i ^ 1], e[i]);
        }
    }
}

假期宿舍

根据题意可得,有一些在校生放假要回家,则他的床位可以给他认识的同学使用。也有放假不想回家的学生他可能会邀请一些非本校的学生来学校玩,问题是床位够不够这些来学校玩的外校生,和放假不回家的本校生使用,注意:一个床只能够一个人使用。也是经典的二分图匹配问题,总共n个学生,设其中需要使用床位的有x个,本校生有m个,学生和床两两配对,问存不存在x个配对使得所有需要勇床位的学生都能够分到床。

直接最大流求二分图匹配,看最后的匹配数是否等于x。

大致见图如下:
网络流(二)最大流之二分图匹配_第2张图片

参考代码:

#include
#include
#include
#include
#define int long long
using namespace std;
const int N = 110, M = 2e4 + 10, inf = 1e18;
int h[N], ne[M], e[M], f[M], idx;
int q[N], d[N], cur[N];
int n, m, S, T;
int w[N];
void add(int a, int b, int c){
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs(){
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S;
    d[S] = 0;
    cur[S] = h[S];
    while(hh <= tt){
        int t = q[hh ++];
        for(int i = h[t]; i != -1; i = ne[i]){
            int ver = e[i];
            // 类似于最短路 将图分层
            if(d[ver] == -1 && f[i]){
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                // 已经找到汇点了
                if(ver == T){
                    return true;
                }
                q[++ tt] = ver;
            }
        }
    }
    return false;
}
int find(int u, int limit){
    // S 到 u 的流量为limit 
    if(u == T){
        return limit;
    }
    // 记录 u 向后流到T的流量
    int flow = 0;
    // 当 flow >= limit 就不用再搜索了, 因为瓶颈在前面一部分
    for(int i = cur[u]; i != -1 && flow < limit; i = ne[i]){
        // 当前弧优化
        cur[u] = i;
        int ver = e[i];
        // 将图分层之后找增广路径时只能从前一层到后面一层的点。
        if(d[ver] == d[u] + 1 && f[i]){
            int t = find(ver, min(limit - flow, f[i]));
            // 不存在增广路径时直接将该点删去
            if(!t){
                d[ver] = -1;
            }
            // 更新残留网络
            f[i] -= t;
            f[i ^ 1] += t;
            flow += t;
        }
    }
    return flow;
}
int dinic(){
    int r = 0, flow;
    while(bfs()){
        while(flow = find(S, inf)){
            r += flow;
        }
    }
    return r;
}
signed main(){
    int my_text;
    scanf("%lld", &my_text);
    while(my_text --){
        scanf("%lld", &n);
        memset(h, -1, sizeof(h));
        memset(w,  0, sizeof(w));
        idx = 0;
        S = 0;
        T = 2 * n + 1;
        int sum = 0;
        for(int i = 1; i <= n  ;i ++){
            scanf("%lld", &w[i]);
            if(w[i] == 1){
                add(i + n, T, 1);
            }
        }
        for(int i = 1; i <= n ; i ++){
            int x;
            scanf("%lld", &x);
            if((w[i] == 1 && x == 0) || (w[i] == 0)){
                add(S, i, 1);
                sum ++;
            }
        }
        for(int i = 1; i <= n ; i ++){
            for(int j = 1; j <= n ; j ++){
                int x;
                scanf("%lld", &x);
                if(x == 1 || i == j){
                    add(i, j + n, 1);
                }
            }
        }
        if(dinic() >= sum){
             printf("^_^\n");
        }
        else{
            printf("T_T\n");
        }
    }
}

圆桌问题

由题意得,在一个可行流中第 i i i个单位派出 r i r_i ri个代表,则有 r i r_i ri 个代表会从该点流出到 n n n张桌子之一的桌子,可以从源点连一条边权为1的边到该点,第 i i i 个桌子可以容纳 c i c_i ci 个代表,可以从该点向汇点连一条容量为 c i c_i ci 的边。

建图大概是这样滴:

网络流(二)最大流之二分图匹配_第3张图片

参考代码:

#include
#include
#include
#include
#define int long long
using namespace std;
const int N = 1e3 + 10, M = 2e5 + 10, inf = 1e18;
int h[N], e[M], ne[M], f[M], idx;
int d[N], q[N], cur[N];
int n, m, S, T;
void add(int a, int b, int c){
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs(){
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S;
    d[S] = 0;
    cur[S] = h[S];
    while(hh <= tt){
        int t = q[hh ++];
        for(int i = h[t]; i != -1; i = ne[i]){
            int ver = e[i];
            if(d[ver] == -1 && f[i]){
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if(ver == T){
                    return true;
                }
                q[++ tt] = ver;
            }
        }
    }
    return false;
}
int find(int u, int limit){
    if(u == T){
        return limit;
    }
    int flow = 0;
    for(int i = cur[u]; i != -1 ;i = ne[i]){
        cur[u] = i;
        int ver = e[i];
        if(d[ver] == d[u] + 1 && f[i]){
            int t = find(ver, min(f[i], limit - flow));
            if(!t){
                d[ver] = -1;
            }
            f[i] -= t;
            f[i ^ 1] += t;
            flow += t; 
        }
    }
    return flow;
}
int dinic(){
    int r = 0, flow;
    while(bfs()){
        while(flow = find(S, inf)){
            r += flow;
        }
    }
    return r;
}
signed main(){
    scanf("%lld%lld",&m, &n);
    memset(h, -1, sizeof(h));
    S = 0;
    T = n + m + 1;
    int sum = 0;
    for(int i = 1; i <= m; i ++){
        int x;
        scanf("%lld",&x);
        add(S, i, x);
        sum += x;
    }
    for(int i = 1; i <= n ; i ++){
        int x;
        scanf("%lld", &x);
        add(i + m, T, x);
    }
    for(int i = 1; i <= m ; i ++){
        for(int j = 1; j <= n ; j ++){
            add(i, j + m, 1);
        }
    }
    if(dinic() == sum){
        printf("1\n");
        for(int i = 1; i <= m ; i ++){
            for(int j = h[i]; j != -1; j = ne[j]){
                if(e[j] > m && e[j] <= n + m && !f[j]){
                    printf("%lld ", e[j] - m);
                }
            }
            printf("\n");
        }
    }
    else{
        printf("0\n");
    }
}

你可能感兴趣的:(图,算法,图论,c++)