P3386 【模板】二分图匹配 · 匈牙利算法 / 网络流 / KM / 费用流

二分图匹配

  • 二分图的判定:没有奇环

  • 二分图的最大匹配

    • 匈牙利算法
    • 网络流
      • 最大流
        • 二分图最大匹配的必须边判定条件: ( x , y ) (x,y) (x,y) 流量为1,并且在残留网络上属于不同的强连通分量
        • 二分图最大匹配的可行边判定条件: ( x , y ) (x,y) (x,y) 流量为1,并且在残留网络上属于同一个的强连通分量
      • 最小割 (割的是边不是点、拆点建边)
  • 二分图带权最大匹配

    • KM
    • 费用流
  • 二分图的覆盖与独立集

    • 最小点覆盖 = 最大匹配包含的边数
    • 最大独立集 = 总点数 - 最大匹配边数
    • 最小边覆盖 = 总点数 - 最大匹配边数
    • 有向无环图的最小路径点覆盖 = 总点数 - 拆点后的最大匹配边数

二分图的最大匹配方案不一定唯一


那些年我用网络流做过的题型:

  • 最小割最少边集
  • 最大点覆盖

板子

匈牙利算法

匈牙利算法入门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
const int INF = 0x3f3f3f3f;

int n, m, k;
namespace Match {//匈牙利算法 O(nm)

    int set_a;//集合a的大小
    int set_b;//集合b的大小

    int mp[N][N];//建图
    int matched[N];//是否已经匹配过
    int vis[N];//本次访问 该点是否被访问过

    bool found(int u) {//dfs找增光路
        for (int i = 1; i <= set_b; ++i) {
            if (mp[u][i]) {
                if (vis[i])continue;
                vis[i] = 1;
                if (!matched[i] || found(matched[i])) {
                    matched[i] = u;
                    return true;
                }
            }
        }
        return false;
    }

    int match() {
        int res = 0;
        memset(matched, 0, sizeof(matched));
        for (int i = 1; i <= set_a; ++i) {
            memset(vis, 0, sizeof(vis));
            if (found(i)) {
                res++;
            }
        }
        return res;
    }

    void init() {
        set_a = n;
        set_b = m;
    }
}
using namespace Match;

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> m >> k;
    set_a = n;
    set_b = m;
    for (int i = 1, u, v; i <= k; ++i) {
        cin >> u >> v;
        if (u > n || v > m)continue;
        mp[u][v] = 1;
    }
    cout << match() << endl;

    return 0;
}

网络流

O ( m n ) O(m\sqrt{n}) O(mn )

// n 10^4~10^5
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;

int n, m, k;
namespace Network_flows { //网络流板子
    //设定起点和终点
    int st;//起点-源点
    int ed;//终点-汇点

    struct egde {
        int to, next;
        int flow;//剩余流量
        //int capacity;//容量
    } e[N * 2];

    int head[N], tot = 1;

    void add(int u, int v, int w) {
        e[++tot] = {v, head[u], w};
        head[u] = tot;
        e[++tot] = {u, head[v], 0};
        head[v] = tot;//网络流反相边流量为0
    }

    int dep[N];//dep[]=-1时为炸点
    queue<int> q;

    bool bfs() {
        memset(dep, 0, sizeof(dep));//顺便起到vis的功能
        q.push(st);
        dep[st] = 1;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (int i = head[u]; i; i = e[i].next) {
                int v = e[i].to;
                if (!dep[v] && e[i].flow) {
                    dep[v] = dep[u] + 1;
                    q.push(v);
                }
            }
        }
        return dep[ed];
    }

    int dfs(int u, int Flow) {
        if (u == ed) return Flow;
        int now_flow = 0;//跑残流
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (dep[v] == dep[u] + 1 && e[i].flow) {
                int f = dfs(v, min(Flow - now_flow, e[i].flow));
                e[i].flow -= f;
                e[i ^ 1].flow += f;
                now_flow += f;
                if (now_flow == Flow) return Flow;
            }
        }
        if (now_flow == 0)dep[u] = -1;
        return now_flow;
    }

#define max_flow dinic

    int dinic() {//最大流
        int res = 0;
        while (bfs()) {
            res += dfs(st, INF);
        }
        return res;
    }

    void init() {
        tot = 1;
        memset(head, 0, sizeof(head));
        while (!q.empty()) q.pop();
    }
}
using namespace Network_flows;

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> m >> k;
    st = 0, ed = n + m + 1;
    for (int i = 1, u, v; i <= k; ++i) {
        cin >> u >> v;
        if (u > n || v > m)continue;
        add(u, v + n, INF);// u的编号1~n 为区分v点(1~m) 加上一个基准值
    }
    for (int i = 1; i <= n; ++i) {
        add(st, i, 1);
    }
    for (int i = 1; i <= m; ++i) {
        add(i + n, ed, 1);
    }

    cout << dinic() << endl;
    return 0;
}

KM

namespace KM {// 带权最大匹配 KM板子 o(n^3)
    // 前提条件:带权最大匹配一定是完备匹配(二分图左右均为n个点且含有n条匹配边)
    int w[N][N];
    int A[N], B[N];//左右部点的顶标
    bool visA[N], visB[N];//访问标记:是否在交错树中
    int match[N];//右部点匹配了哪一个左部点
    int delta;

    bool dfs(int x) {
        visA[x] = 1;
        for (int y = 1; y <= n; y++) {
            if (!visB[y]) {
                if (A[x] + B[y] - w[x][y] == 0) {//相等子图
                    visB[y] = 1;
                    if (!match[y] || dfs(match[y])) {
                        match[y] = x;
                        return true;
                    }

                }
            } else {
                delta = min(delta, A[x] + B[y] - w[x][y]);
            }
        }
        return false;
    }

    int KM() {
        for (int i = 1; i <= n; i++) {
            A[i] = -INF;
            B[i] = 0;
            for (int j = 1; j <= n; j++) {
                A[i] = max(A[i], w[i][j]);
            }
        }

        for (int i = 1; i <= n; i++) {
            while (1) {
                memset(A, 0, sizeof A);
                memset(B, 0, sizeof B);
                delta = INF;
                if (dfs(i)) break;
                for (int j = 1; j <= n; j++) {
                    if (A[j]) A[j] -= delta;
                    if (B[j]) B[j] += delta;
                }
            }
        }

        int res = 0;
        for (int i = 1; i <= n; i++) {
            res += w[match[i]][i];
        }
        return res;
    }
}
using namespace KM;

费用流

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;
int n, m, k;
namespace cost_flows { //最小费用最大流板子
    //设定起点和终点
    int st;//起点-源点
    int ed;//终点-汇点

    struct egde {
        int to, next;
        int flow;//剩余流量
        int cost; //花费
    } e[N * 2];

    int head[N], tot = 1;

    void add(int u, int v, int w, int c) {
        e[++tot] = {v, head[u], w, c};
        head[u] = tot;
        e[++tot] = {u, head[v], 0, -c};
        head[v] = tot;//网络流反相边流量为0 费用是-cost
    }

    queue<int> q;

    int flow[N];//源点到此处的流量
    int vis[N];//是否在队里里
    int last[N];//每个点所连的前一条边
    int cost[N];//最小花费
    int pre[N];//每个节点的前驱节点

    bool spfa() {
        memset(flow, INF, sizeof(flow));
        memset(cost, INF, sizeof(cost));//注意了 这里是最大值
        memset(vis, 0, sizeof(vis));

        q.push(st);

        cost[st] = 0;
        vis[st] = 1;
        pre[ed] = -1;//ok

        while (!q.empty()) {
            int u = q.front();
            q.pop();
            vis[u] = 0;

            for (int i = head[u]; i; i = e[i].next) {
                int v = e[i].to;
                if (cost[v] > cost[u] + e[i].cost && e[i].flow) {// 最小费用
                //if (cost[v] < cost[u] + e[i].cost && e[i].flow) { // 最大费用
                    cost[v] = cost[u] + e[i].cost;
                    pre[v] = u; // 前驱节点
                    last[v] = i; // u->v 对应的边
                    flow[v] = min(flow[u], e[i].flow);

                    if (!vis[v]) {
                        vis[v] = 1;
                        q.push(v);
                    }
                }
            }
        }
        return pre[ed] != -1;//即 没有到达汇点
    }


    int max_flow, min_cost;

    void MCMF() {
        while (spfa()) {
            int now = ed;
            max_flow += flow[ed];
            min_cost += flow[ed] * cost[ed];
            while (now != st) {
                //从源点一直回溯到汇点
                e[last[now]].flow -= flow[ed];
                e[last[now] ^ 1].flow += flow[ed];
                now = pre[now];
            }
        }
    }


    void init() {
        tot = 1;
        memset(head, 0, sizeof(head));
        while (!q.empty()) q.pop();
         max_flow=0;
         min_cost=0;
    }
}
using namespace cost_flows;
//最大流量只有一个 流法却有很多种 即要保持最大流又要保持最小费用 所以需要最短路
//由于dijkstra不能跑负权边 所以基本上用的都是spfa
//但是如果可以采用一种方法加上一个基准值base 将所有数据都跑在整数的话就可以了

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> m >> st >> ed;
    for (int i = 1,u,v,w,c; i <= m; ++i) {
        cin>>u>>v>>w>>c;
        add(u,v,w,c);
    }
    MCMF();
    cout <<max_flow<<" "<<min_cost << endl;

    return 0;
}

dijkstra版本

#include<bits/stdc++.h>

typedef long long ll;
using namespace std;
const int MAXN = 1e5;
const int INF = 0x7fffffff;

struct edge {
    int to, capacity, cost, rev;

    edge() {}

    edge(int to, int _capacity, int _cost, int _rev) : to(to), capacity(_capacity), cost(_cost), rev(_rev) {}
};

struct Min_Cost_Max_Flow {
    int V, H[MAXN + 5], dis[MAXN + 5], PreV[MAXN + 5], PreE[MAXN + 5];
    vector<edge> G[MAXN + 5];

    //调用前初始化
    void Init(int n) {
        V = n;
        for (int i = 0; i <= V; ++i)G[i].clear();
    }

    //加边
    void Add_Edge(int from, int to, int cap, int cost) {
        G[from].push_back(edge(to, cap, cost, G[to].size()));
        G[to].push_back(edge(from, 0, -cost, G[from].size() - 1));
    }

    //flow是自己传进去的变量,就是最后的最大流,返回的是最小费用
    int Min_cost_max_flow(int s, int t, int f, int &flow) {
        int res = 0;
        fill(H, H + 1 + V, 0);
        while (f) {
            priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
            fill(dis, dis + 1 + V, INF);
            dis[s] = 0;
            q.push(pair<int, int>(0, s));
            while (!q.empty()) {
                pair<int, int> now = q.top();
                q.pop();
                int v = now.second;
                if (dis[v] < now.first)continue;
                for (int i = 0; i < G[v].size(); ++i) {
                    edge &e = G[v][i];
                    if (e.capacity > 0 && dis[e.to] > dis[v] + e.cost + H[v] - H[e.to]) {
                        dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
                        PreV[e.to] = v;
                        PreE[e.to] = i;
                        q.push(pair<int, int>(dis[e.to], e.to));
                    }
                }
            }
            if (dis[t] == INF) break;
            for (int i = 0; i <= V; ++i)H[i] += dis[i];
            int d = f;
            for (int v = t; v != s; v = PreV[v]) d = min(d, G[PreV[v]][PreE[v]].capacity);
            f -= d;
            flow += d;
            res += d * H[t];
            for (int v = t; v != s; v = PreV[v]) {
                edge &e = G[PreV[v]][PreE[v]];
                e.capacity -= d;
                G[v][e.rev].capacity += d;
            }
        }
        return res;
    }

    int Max_cost_max_flow(int s, int t, int f, int &flow) {
        int res = 0;
        fill(H, H + 1 + V, 0);
        while (f) {
            priority_queue<pair<int, int>> q;
            fill(dis, dis + 1 + V, -INF);
            dis[s] = 0;
            q.push(pair<int, int>(0, s));
            while (!q.empty()) {
                pair<int, int> now = q.top();
                q.pop();
                int v = now.second;
                if (dis[v] > now.first)continue;
                for (int i = 0; i < G[v].size(); ++i) {
                    edge &e = G[v][i];
                    if (e.capacity > 0 && dis[e.to] < dis[v] + e.cost + H[v] - H[e.to]) {
                        dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
                        PreV[e.to] = v;
                        PreE[e.to] = i;
                        q.push(pair<int, int>(dis[e.to], e.to));
                    }
                }
            }
            if (dis[t] == -INF)break;
            for (int i = 0; i <= V; ++i)H[i] += dis[i];
            int d = f;
            for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity);
            f -= d;
            flow += d;
            res += d * H[t];
            for (int v = t; v != s; v = PreV[v]) {
                edge &e = G[PreV[v]][PreE[v]];
                e.capacity -= d;
                G[v][e.rev].capacity += d;
            }
        }
        return res;
    }
} MCMF;

int main() {
    while (scanf("%d", &n)) {
        //	MCMF.Init(n); 初始化 邻接表
        //MCMF.Add_Edge(begin, end, flow, cost);   
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值