图论专题——二分图

本文介绍了二分图的概念及其在图论中的重要性质,包括如何通过染色法和并查集判断一个图是否为二分图。接着讨论了二分图的匹配问题,如匈牙利算法的应用,以及二分图在最小点覆盖、最大独立集和最小路径覆盖问题中的角色。这些理论在解决实际问题时具有广泛的应用价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.二分图的概念

离散数学书上是这样写的:
在这里插入图片描述
二分图的充分必要条件:无奇圈 ( n ≥ 2 ) (n\ge2) (n2)
二分图不一定是连通的

2.判定二分图

题目_染色法判定二分图

题目链接 Acwing 860
在这里插入图片描述

思路

在这里插入图片描述
用两种颜色来染色,相邻节点不能相同颜色
本人喜欢用bfs来写

代码 O ( N ) O(N) O(N)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<map>
#include<unordered_map>
#include<queue>

#define me(x,y) memset(x,y,sizeof x)
#define rep(i,x,y) for(i=x;i<=y;++i)
#define repf(i,x,y) for(i=x;i>=y;--i)
#define lowbit(x) -x&x
#define inf 0x3f3f3f3f
#define INF 0x7fffffff

using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> vc;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

struct edge
{
    int to;
    int nxt;
};

const int N=1e5+10;
int n,m,cnt;

int head[N];
edge table[N*2];
int col[N],q[N];

inline void add(int& u,int& v)
{
    table[++cnt].nxt=head[u];
    head[u]=cnt;
    table[cnt].to=v;
}

bool bfs(int x)
{
    int u,v,e,hh=0,tt=-1;
    
    q[++tt]=x,col[x]=0;
    while(hh<=tt)
    {
        u=q[hh++];
        for(e=head[u];e;e=table[e].nxt)
        {
            v=table[e].to;
            //若该点被染色,则相连的点颜色必须相反
            if(col[v]!=-1&&col[u]==col[v])  return false;
            //未被染色,则染上相反的颜色,并入队
            if(col[v]==-1)  col[v]=col[u]^1,q[++tt]=v;
        }
    }
    return true;
}

int main()
{
    int i,u,v;
    bool flag=true;
    me(col,-1);
    
    n=read(),m=read();
    while(m--)
    {
        u=read(),v=read();
        add(u,v),add(v,u);
    }
    
    rep(i,1,n)
    {
        if(col[i]==-1)
        {
            flag=bfs(i);
            if(!flag)   break;
        }
    }
    
    if(flag)    puts("Yes");
    else puts("No");
    
    return 0;
}
题目_并查集判定二分图

Leetcode785
思路:对于图中的某个顶点的所有相邻顶点,首先判断有无与该顶点处于同一集合的,有则不是二分图,最后两两合并相邻顶点。

class Solution {
    int siz;
    int[] fa;

    private int find(int x)
    {
        return (fa[x]==x) ? x : (fa[x]=find(fa[x]));
    }

    private void merge(int i,int j)
    {
        fa[find(i)]=find(j);
    }
    
    public boolean isBipartite(int[][] graph)
    {
        siz=graph.length;
        fa=new int[siz+5];

        for(int i=0;i<siz;++i)    fa[i]=i;  

        for(int i=0;i<siz;++i)
        {
            int l=graph[i].length;
            for(int j=0;j<l;++j)    if(find(i)==find(graph[i][j]))  return false;
            for(int j=0;j<l-1;++j)  merge(graph[i][j],graph[i][j+1]);
        } 

        return true;
    }
}

3.二分图匹配:匈牙利算法

题目

题目链接 Acwing 861
在这里插入图片描述

思路

不必理解严格证明,可以用一个简单的贪心思想来理解,即能多匹配一个是一个!!!!
关键是理解匈牙利算法的模板!!

代码 O ( N M ) O(NM) O(NM) 但通常情况下效率较高
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<map>
#include<unordered_map>
#include<queue>

#define me(x,y) memset(x,y,sizeof x)
#define rep(i,x,y) for(i=x;i<=y;++i)
#define repf(i,x,y) for(i=x;i>=y;--i)
#define lowbit(x) -x&x
#define inf 0x3f3f3f3f
#define INF 0x7fffffff

using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> vc;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

struct edge
{
    int to;
    int nxt;
};

const int N= 1010,M=1e5+10,OF=505;
int n1,n2,m,cnt;

int head[N],match[N];
edge table[M];
bool st[N];

inline void add(int& u,int v)
{
    table[++cnt].nxt=head[u];
    head[u]=cnt;
    table[cnt].to=v;
}

/*
两个核心数组:
st 数组,在对每个boy的匹配过程中,预定的作用
match数组,每个girl的匹配boy
*/

bool find(int u)            //递归匹配函数,尝试为boy u进行匹配
{
    int v,e;
    
    for(e=head[u];e;e=table[e].nxt)
    {
        v=table[e].to;      //当前boy想要的一个女孩
        
        if(st[v])   continue;       //若被当前这轮预定了,则不能选了
        st[v]=true;                 //没预定,则预定
        
        if(!match[v]||find(match[v]))       //该女孩没被选,或者能够融通
        {
            match[v]=u;
            return true;
        }
    }
    return false;
}

int main()
{
    int i,u,v,ans=0;
    
    n1=read(),n2=read(),m=read();
    rep(i,1,m)
    {
        u=read(),v=read();
        add(u,v+OF);
    }
    
    rep(i,1,n1)
    {
        me(st,false);
        if(find(i)) ++ans;
    }
    
    printf("%d",ans);
	
	return 0;
}

4.最小点覆盖问题

简单理解最小点覆盖,就是二分图中每个边至少一个端点在该点集中的最小点集
结论:在二分图中,最小点覆盖==最大匹配
证明:同时证明 ≤ \le ≥ \ge 即可证明 = = == ==,证明如下:
在这里插入图片描述

例题:

在这里插入图片描述
题目链接
分析:简单理解一下就是最小点覆盖问题,注意特判0,代码略

5.最大独立集问题

简单理解:给一无向图,找出一个点集使得任意两点之间都没有连边,这个点集就是独立集。而点最多的独立集,就是最大独立集。在普通的无向图中,求最大独立集是NP难问题,但在二分图中,最大独立集==n - 最大匹配数
证明
在这里插入图片描述

例题:

在这里插入图片描述
题目链接
分析:首先,棋盘上的点可以分为奇数点偶数点,而由于马走日,符合二分图的定义。所以可以把棋盘抽象成二分图,相互攻击的点之间奇偶性一定不同,问的就是最大独立集合问题,代码略

6.最小路径覆盖问题

简单理解: 针对一个有向无环图(DAG),用最少条互不相交路径,覆盖所有点
重要结论1: 最小不重复路径 = n-拆点后二分图最大匹配数
重要结论2: 最小重复路径 = n-拆点传递闭包后二分图最大匹配数
懒得写了,引用一个大佬的题解
最小不重复路径:
在这里插入图片描述
最小重复路径:
在这里插入图片描述

例题

在这里插入图片描述
思路:本题就是个最小路径重复覆盖,可以这样理解,若不是最小覆盖,则一定有两个路径可以合并成一个更长的路径(原本的两个短路径,可以相互看见,不满足原题的条件)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<map>
#include<unordered_map>
#include<queue>

#define me(x,y) memset(x,y,sizeof x)
#define rep(i,x,y) for(i=x;i<=y;++i)
#define repf(i,x,y) for(i=x;i>=y;--i)
#define lowbit(x) -x&x
#define inf 0x3f3f3f3f
#define INF 0x7fffffff

using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> vc;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

const int N= 210,M=3e4+10;
int n,m;

bool st[N],mat[N][N];
int match[N];

bool find(int u)
{
	int v;
	
	rep(v,1,n)
	{
		if(!mat[u][v])	continue;
		if(st[v])	continue;
		st[v]=true;
		if(!match[v]||find(match[v]))	
		{
			match[v]=u;
			return true;
		}
	}
	return false;
}

int main()
{
	int i,j,k,u,v,ans=0;
	
	n=read(),m=read();
	rep(i,1,m)	u=read(),v=read(),mat[u][v]=true;
	
	rep(k,1,n)	rep(i,1,n)	rep(j,1,n)	mat[i][j]|=mat[i][k]&mat[k][j]; 
	
	rep(i,1,n)
	{
		me(st,false);
		if(find(i))	++ans;
	}
	
	printf("%d",n-ans);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值