二分图(最小顶点覆盖、最大匹配、最大独立集)

本文参考于:https://www.cnblogs.com/czsharecode/p/9777533.html

一、二分图的基本概念

  二分图又称为二部图,是图论的一种特殊模型。
  设 G=(V,E)是一个无向图,如果顶点 V 可以分割为两个互不相交的子集 (A,B),并且图中的每条边 (i,j) 所关联的顶点 i 和 j 分别属于这两个不同的顶点集 (i in A,j in B) ,则称图 G 为一个二分图。
二分图
  也就是说,只要这两个点之间有边,那么这两个点就不能同属一个集合,必须分在两边。
  这也就带来一个问题,并不是所有的无向图 G 都能转化为二分图。

二、二分图的判定

  定理:无向图 G 为二分图的充要条件是,G 至少有两个顶点,且其所有回路的长度均为偶数。
  判定方法:根据定义比根据定理更好判断,只需要从第一个点开始,将其染成颜色1,则与其所有相连的所有顶点都染成颜色2,如果与其相连的点已经被染成颜色1,则图G不是二分图。
  模板题目:https://www.acwing.com/problem/content/862/
  把该点的所有终点当作新的起点,重复以上过程。很显然,既可以用 dfs 染色,有可以用 dfs 染色。

vector<int> G[maxn];  //图
int n,m;  //顶点数
int col[maxn];    //顶点的颜色 1|-1
//把顶点染成1或者-1
bool DFS(int v,int c)
{
    col[v] = c;   //把顶点v染成颜色c
    for(int i=0;i<G[v].size();i++){
        int to = G[v][i];
        //如果相邻顶点同色,则返回false
        if(col[to] == c) return false;
        //如果相邻顶点没有染色,则染成 -c
        if(col[to]==0&&!DFS(to,-c)) return false;
    }
    //如果所有顶点都染过色,就返回true
    return true;
}
bool solve()
{
    for(int i=1;i<=n;i++){
        if(col[i]==0){
            //如果顶点i还没被染色,则染成 1
            if(!DFS(i,1)){
                return false;
            }
        }
    }
    return true;
}

bfs染色代码

int n,m;
vector<int> G[maxn];
int col[maxn] ;
bool bfs(int s)
{
    col[s] = 1;
    queue<int> que;
    que.push(s);
    while(!que.empty()){
        int from = que.front();
        que.pop();
        for(int i=0;i<G[from].size();i++){
            int to = G[from][i];
            if(to==from)    continue;
            // 如果相邻的点没有上色就给这个点上色
            if(col[to]==0){
                que.push(to);
                col[to] = -col[from];
            }
            // 如果相邻的颜色相同则返回false
            if(col[to]==col[from])
                return false;
        }
    }
    // 如果所有的点都被染过色,且相邻的点颜色都不一样,返回true
    return true;
}
bool solve(){
    for(int i=1; i<=n; i++){
        if(col[i] == 0)
            if(!bfs(i)){
                return false;
            }
    }
    return true;
}
二分图的最大匹配

定义:在一个无向图中,定义一条边覆盖的点为这条边的两个端点。
找到一条边集 S 包含最多的边,使得这个边覆盖到所有顶点中的每一个点只被一条边覆盖。 S 的大小叫做图的最大匹配。

通俗地讲,比如说有一场宴会,男孩和女孩跳舞,并且他们必须相互喜欢才能在一起跳舞,一个男孩可能喜欢 0 个或多个女孩,一个女孩也可能喜欢 0 个或多个男孩,但一个男号和他喜欢的 女孩跳舞之后就不能和其它他喜欢的女孩跳舞,女孩也是如此。请问最多可以多少对男孩女孩一起跳舞。

很显然,互相喜欢的男孩女孩建边得到一个二分图,求一个一个边集 S 包含最多的边,使得这个边覆盖到所有顶点中的每个顶点只被一条边覆盖,即求二分图的最大匹配。

[匈牙利算法]
把二分图分为 A,B 两个集合,依次枚举 A 中的每个点,尝试在 B 集合中找到一个匹配。

对于一个 A 集合中一个 x,假设 B 集合有一个与其相连的点 y,若 y 暂时还没有匹配点,那么 x 可以和 y 匹配,找到;

否则,设 y 已经匹配到点 z,(显然 z 是 A 集合中的一个点),那么,我们将尝试为 z 找一个除了 y之外的匹配点,若找到,那么 x 可以和 y 匹配,否则 x 和 y 不匹配。

【图解】
我们以下图为例说明匈牙利匹配算法
在这里插入图片描述
st1:从 1 开始,找到右侧的点 4,发现点 4没有被匹配。得到下图:
在这里插入图片描述
st2:接下来,从 2 开始,试图在右边找到一个它的匹配点,我们枚举 5,发现 5 还没有被匹配,于是找到了 2 的匹配点为 5,得到如下图:
在这里插入图片描述
st3:接下来,我们找 3 的匹配点。我们枚举了 5,发现 5 已经有了匹配点 2。此时,我们试图找到 2 除 5 以外的另一个匹配点,我们发现,我们可以找到 7,于是 2 可以匹配到 7,所以 5 可以匹配给 3,得到下图:
在这里插入图片描述
此时,结束,我们得到最大匹配为 3。

【算法核心】

每次寻找可以匹配 A 点的一个点 B,如果这个点 B 还没有被匹配,暂时就把这个点 B 当作 A 的匹配点;如果这个 B 在之前已经匹配了 C,那就看 C 能不能匹配除了 B 以外的未匹配点,如果找不到则重复以上过程直到找到或者枚举所有可能点还找不到,结束点A的匹配;如果找到,则把 B 匀给 A(本来 B 是 C 的,现在 C 说我还有其他舞伴,B 就让给 A 了)。这样就能多出一个匹配,相当于找到一条“增广路径”。
【复杂度】
时间复杂度:邻接矩阵:最坏为O(n3),邻接表:O(nm)
空间复杂度 :邻接矩阵:O(n2),邻接表:O(n+m)

int G[maxn][maxn],mh[maxn];
bool vis[maxn];
int m,n;	// m代表图左边一列,n代表图右边一列
bool dfs(int x)
{
    for(int y=1;y<=n;y++){
        if(G[x][y]==0) continue;
        if(!vis[y]){
            vis[y] = true;
            if(!mh[y]||dfs(mh[y])){
                mh[y] = x;
                return true;
            }
        }
    }
    return false;
}

int ans = 0;
for(int i=1;i<=m;i++){
	memset(vis,false,sizeof(vis));
	if(dfs(i))  ans++;
}

POJ 1469 COURSES
已知,有N个学生和P门课程,每个学生可以选0门,1门或者多门课程,要求在N个学生中选出P个学生使得这P个学生与P门课程一一对应。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<ctime>
#include<map>
#define ll long long
#define ld long double
#define ull unsigned long long
using namespace std;
const int maxn = 510;
int G[maxn][maxn];
int match[maxn];
bool vis[maxn];
int n,m;
bool dfs(int x)
{
    for(int y=1;y<=m;y++){
        if(G[x][y]==0) continue;
        if(!vis[y]){
            vis[y] = true;
            if(!match[y]||dfs(match[y])){
                match[y] = x;
                return true;
            }
        }
    }
    return false;
}
int main(void)
{
    int t,p;
    scanf("%d",&t);
    for(int k=1;k<=t;k++){
        scanf("%d%d",&m,&n);
        memset(G,0,sizeof(G));
        memset(match,0,sizeof(match));
        int x;
        for(int y=1;y<=m;y++){
            scanf("%d",&p);
            while(p--){
                scanf("%d",&x);
                G[x][y] = 1;
            }
        }
        int ans = 0;
        for(int i=1;i<=n;i++){
            memset(vis,false,sizeof(vis));
            if(dfs(i))  ans++;
        }
        if(ans==m)  printf("YES\n");
        else    printf("NO\n");
    }
    return 0;
}

二分图的最小顶点覆盖

定义:假设选了一个点就相当于覆盖了以它为端点的所有边。最小顶点覆盖就是个选择最少的点来覆盖所有的边。
定义:最小顶点覆盖等于二分图的最大匹配。

二分图的最大独立集

定义:选出一些顶点使得这些顶点两两不相邻,则这些点构成的集合称为独立集。找出一个包含最多的独立集称为最大独立集。
定理:最大独立集 = 所有顶点数 - 最小顶点覆盖 = 所有顶点数 - 最大匹配
【模板】
建边是相反意义建边,把有冲突关系的两个点建边,再套用最大匹配的模板,用所有顶点数减之即可。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逃夭丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值