P3386 【模板】二分图最大匹配

28 阅读4分钟

P3386 【模板】二分图最大匹配

这是一道匈牙利算法的板子题,那就在这里总结一下二分图以及匈牙利算法吧。

1.二分图

二分图就是一张图可以将点分为两个阵营,并且同一阵营中的点之间没有连线。

如上图就是一张二分图,两个阵营的点只会向另一阵营的点连线,并且一张二分图不能存在奇数环。

2.利用染色法确定是否是二分图

想要确定一张图是不是二分图可以用染色法,就是设一个数组将每一个点分为“1”和“2”两个阵营,即一个点若是“1”阵营则它周边一圈的点都是“2”阵营,反之亦然,通过这样宛如染色的方法来划分点之间的阵营,既然如此可以直接用bfs或者dfs来遍历,判断二分图的代码如下

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

int h[N],e[N],ne[N],idx;
int color[N];       //下表存点,元素存点的阵营 

void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

bool dfs(int u,int c)
{
    color[u] = c;       //染色 
    for(int i = h[u] ; i != -1 ; i = ne[i])     //遍历这个点周边的点 
    {
        int j = e[i];
        if(!color[j])       //若周边的点也没有染色 
        {
            if(!dfs(j , 3 - c))      //通过赋值“3 - c”来将“1”周围点染成“2”,反之亦然 
            {
                return 0;
            }
        }else if(color[j] == c)    //如果遇见周围的点同色则return 0 
        {
           return 0;
        }
    }
    
    return 1;
}

int main()
{
    memset(h , -1 , sizeof h);
    int n,m;
    cin>>n>>m;
    
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        add(a , b);
        add(b , a);     //设置为无向图 
    }
    
    bool flag = 1;      //标记 
    for(int i = 1 ; i <= n ; i++)
    {
        if(!color[i])        //如果遇见未染色的点,则进行dfs检查 
        {
            if(!dfs(i , 1))    //遇见未染色的点先将他分为”1“阵营 
            {
                flag = 0;
                break;
            }
        }
    }
    
    if(flag)
    {
        cout<<"Yes";
    }else
    {
        cout<<"No";
    }
}

3.二分图的最大匹配(匈牙利算法)

二分图的匹配就是任意两边都没有公共端点的边的集合,即给定一个二分图 G,在 G的一个子图 M中,M的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M是一个匹配。

二分图的最大匹配就是所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

如上图红线都是都是一组成功的匹配(左边的第一个点与右边第四个点也是红线)。

过一遍流程:

左一先与右二匹配,然后左二与右一匹配,左三只想与右二匹配,可是右二已经与左一匹配,这个时候返回左一看一下左一是否能换一个对象,发现左一也想与右四匹配,所以左一撤回右二的线改连右四,然后左三也成功连结右二,最后左四连结右三,一共4对匹配圆满结束。

以上便是匈牙利算法的流程,接下来的注释会在代码中,上代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

int h[N],e[N],ne[N],idx;
bool st[N];      //记录一轮中右阵营是否匹配过,所以每一轮都要初始化 
int match[N];    //下表是右阵营的点,元素是与之匹配的左阵营的点 

void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

bool find(int x)
{
    for(int i = h[x] ; i != -1 ; i = ne[i])
    {
        int j = e[i];
        if(!st[j])           //若右阵营的该点还没有出现过   
        {
            st[j] = 1;
            if(!match[j] || find(match[j]))    // 如果右阵营的j点还没有匹配的点或者是原来与之匹配的点可以找到新欢 
            {
                match[j] = x;        //进行匹配 
                return 1;
            }
        }
    }
    
    return 0;
}

int main()
{
    int n,m,k;
    memset(h , -1 , sizeof h);
    cin>>n>>m>>k;
    
    while(k--)
    {
        int a,b;
        cin>>a>>b;
        add(a , b);      //之后只遍历一边,所以有向边即可 
    }
    
    int res = 0;       //记录成功匹配数 
    for(int i = 1 ; i <= n ; i++)    //遍历左阵营 
    {
        memset(st , 0 , sizeof st);  //每一轮刷新右阵营 
        if(find(i))
        {
            res++;
        }
    }
    
    cout<<res;
}