zoukankan      html  css  js  c++  java
  • 浅析二分图最大匹配

    二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图,如图2所示

    匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。例如,图 3、图 4 中红色的边就是图 2 的匹配

          

    我们定义匹配点匹配边未匹配点非匹配边,它们的含义非常显然。例如图 3 中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。

    最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。图 4 是一个最大匹配,它包含 4 条匹配边。

    基本概念讲完了。求解最大匹配问题的一个算法是匈牙利算法,下面讲的概念都为这个算法服务。

    交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。

    增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。例如,图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出):

     

    增广路有一个重要特点:非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。

    我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。匈牙利算法正是这么做的,一般来说可以用$DFS/BFS$实现

    下面对$DFS$稍做说明:

    因为增广路长度为奇数,路径起始点非左即右,所以我们先考虑从左边的未匹配点找增广路。 注意到因为交错路的关系,增广路上的第奇数条边都是非匹配边,第偶数条边都是匹配边,于是左到右都是非匹配边,右到左都是匹配边。于是我们给二分图 定向,问题转换成,有向图中从给定起点找一条简单路径走到某个未匹配点,此问题等价给定起始点$s$能否走到终点$t$。那么只要从起始点开始 $DFS$ 遍历直到找到某个未匹配点,$O(m)$。枚举$n$个点,复杂度为$O(n*m)$

    对于所有增广路径来说,它的长度为奇数,起点和终点必然分别在两个集合中,可以规定所有起点都在左集合中,则当左集合的点增广路寻找完后,整个算法就结束了。因此我们建图的时候只用建立左集合到右集合的单向边即可,标记匹配边的时候也只用标记右到左的边。

    可以结合下面的过程图理解下:

    #include<iostream>
    using namespace std;
    const int maxn = 2e5+100;
    int n, m, k, ans;
    int head[1010], tot;
    int vis[2020], match[1010], u, v, dfn;
    struct node{
        int to, nxt;
    }e[maxn<<1];
    void add(int u, int v){
        e[++tot].to = v;
        e[tot].nxt = head[u];
        head[u] = tot;
    }
    bool dfs(int u){
        for(int i = head[u]; i ; i = e[i].nxt){
            int v = e[i].to;
            if(vis[v]!=dfn){ //这一轮还没有遍历到
                vis[v] = dfn;
                if(!match[v]||dfs(match[v])){ //找到了一条增广路径
                    match[v] = u;//匹配边v->u 
                    return true;
                }
            }
                  
        }
        return false;
    }
    int main(){
        cin >> n >> m >> k;
        for(int i = 1; i <= k; i++){
            cin >> u >> v;
            add(u, v);
        }
        for(int i = 1; i <= n; i++){
            dfn++;//小技巧,用于每轮寻找增广路,标记是否被访问过 
            if(dfs(i)) ans++;//找到了一条增广路
        }    
        cout << ans << endl;
    }
    View Code

    Reference:

    https://oi-wiki.org/graph/bi-graph/

    https://oi-wiki.org/topic/graph-matching/bigraph-match/

    https://www.renfei.org/blog/bipartite-matching.html

    https://www.cnblogs.com/czsharecode/p/9777533.html

    https://www.cnblogs.com/penseur/archive/2013/06/16/3138981.html

    https://www.cnblogs.com/jianglangcaijin/p/6035950.html

    https://www.luogu.com.cn/blog/Repulser/solution-p3386

    https://www.luogu.com.cn/blog/OnePunchManGO/solution-p3386

     

  • 相关阅读:
    E
    D
    C
    B
    Python
    IDEA 设置资源目录
    光猫指示灯含义
    IO模型
    Linux 总目录
    Linux python 使用
  • 原文地址:https://www.cnblogs.com/wizarderror/p/13304781.html
Copyright © 2011-2022 走看看