二分图

二分图概念
顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集。
无向图G为二分图的充分必要条件
G至少有两个顶点,且其所有回路的长度均为偶数。
最大匹配
给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配.
选择这样的边数最大的子集称为图的最大匹配问题.
最小覆盖
最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。最小覆盖=最大匹配。
简单路径:
如果一条路径上的顶点除了起点和终点可以相同外,其它顶点均不相同,则称此路径为一条简单路径;起点和终点相同的简单路径称为回路(或环)。
最小路径覆盖:
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。
增广路(增广轨):
若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径(举例来说,有A、B集合,增广路由A中一个点通向B中一个点,再由B中这个点通向A中一个点……交替进行)。
增广路径的性质:
1 有奇数条边。
2 起点在二分图的左半边,终点在右半边。
3 路径上的点一定是一个在左半边,一个在右半边,交替出现。
4 整条路径上没有重复的点。
5 起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。
6 路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。
7 最后,也是最重要的一条,把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。
匈牙利算法的基本模式:
1、 初始时最大匹配为空
2、 while (找得到增广路径)
3、 do 把增广路径加入到最大匹配中。
二分图匹配中较为重要的三个公式:
二分图最小顶点覆盖 = 二分图最大匹配;
DAG图的最小路径覆盖 = 节点数(n)- 最大匹配数;
二分图最大独立集 = 节点数(n)- 最大匹配数;

样例:

/*
输入:第一行n,m,k,代表左子集有n个点,编号1~n,右子集有m个点,编号1~m,左右子集之间有k个关系。
然后有n行,每行有两个数a,b。表示a,b之间相连。
求二分图的最大匹配。
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int map[101][101];
int link[101]; //匹配结果中右子集的点的前驱,比如说link[5]=3,
              //代表右子集的5节点的前驱是左子集的3节点。
int vis[101];//标记右子集的节点在找当前增广链是是否找过
int n,m,k,s;
int find(int x)
{
    for(int i=1;i<=m;i++)
    {
        if(map[x][i]&&!vis[i])//当前节点在此次寻找增广链的过程中未被访问过。
        {
            vis[i]=1;
            if(link[i]==0||find(link[i]))
            //当前右节点没有前驱,或者在往前的寻找过程中找到了增广链
            {
                link[i]=x;//如果找到了增广链,那么if成立,这条增广链上的所有右节点才会被标记前驱。
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    int a,b;
    memset(link,0,sizeof(link));
    memset(map,0,sizeof(map));
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<k;i++)
    {
        scanf("%d%d",&a,&b);
        map[a][b]=1;//标记图中a,b的关系
    }
    s=0;
    for(int i=1;i<=n;i++)//依次从左子集的点找增广链
    {
        memset(vis,0,sizeof(vis));
        if(find(i))s++;//存在增广链,匹配结果加1.
    }
    cout<<s<<endl;
    return 0;
}


原文地址:https://www.cnblogs.com/jiangu66/p/3217737.html