二分图全面学习笔记

news/2024/10/11 21:21:00

二分图全面学习笔记

Part1:二分图的定义与判定方法

首先,我们要知道二分图的定义是什么。

二分图的定义

​ 如果一张无向图的 \(n\) 个节点可以分成 \(A,B\) 两个不相交的非空集合,并且同一个集合之中的两个点之间没有边相连接,那么称该无向图为二分图 (Bipartite Graph)

举个栗子

Snipaste_2024-08-29_19-45-50

很显然的,这个图是一个二分图

那么,二分图又有什么性质呢?

二分图的性质

  • 同一个集合之中的点之间没有边相连接
  • 图上没有奇环
    所谓奇环,就是长度为奇数的环。显然,由于一个集合内的点之间没有直接的边相连,所以从这个点出发之后一定要经过偶数条边才能回到这个集合。所以说,二分图上没有奇环。

所以,知道了这些,我们该如何判定二分图呢?

二分图的判定方法之:染色法

我们可以用染色法来判定二分图。

即我们用两种颜色来标记图上的点,当一个点 \(i\) 被标记后,用不同的颜色标记所有与之相邻的点。这是因为所有与一个点有直接连接的点都要在另一个集合之中,所以需要染上另一种颜色。

如果有一个与 \(i\) 相邻的点和 \(i\) 染的颜色相同,即说明在这个集合内有两个点有直接的边相连,那么说明这个图不是二分图。然后将这个过程递归的进行下去,直到所有节点都被染上颜色为止。

这个过程可以用 BFS 或者 DFS 实现

代码如下

点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct edge
{int f,t;
};
edge edges[100000];
struct node
{int num;int col;vector<int> to;};
node nodes[100000];
int n,m;
int tag=1;
bool continue_tag=1;//是否有两个相同节点染上了相同颜色 
void dfs(int x)//x 表示现在在哪个节点
{int col=nodes[x].col;int to;for(int yy=0; yy<nodes[x].to.size(); yy++){if(continue_tag)//如果还要继续的话 {to=edges[nodes[x].to[yy]].t;if(nodes[to].col==0)//如果还没有染色过 {nodes[to].col=3-col;//染上相反颜色 dfs(to);//递归遍历 }else//被染色过 {if(nodes[to].col==col)//如果颜色相同 {continue_tag=0;//已经不是二分图了,所以不用再继续了 return;}}}}
}
int main()
{ios::sync_with_stdio(0);cin>>n>>m;int a,b;for(int yy=1; yy<=m; yy++){cin>>a>>b;edges[yy].f=a;edges[yy].t=b;edges[yy+m].f=b;edges[yy+m].t=a;nodes[a].to.push_back(yy);nodes[b].to.push_back(yy+m);}for(int ww=1; ww<=n; ww++)//防止图不连通 {if(nodes[ww].col==0){continue_tag=1;dfs(ww);if(continue_tag==0){tag=0;break; }}}if(tag==0){cout<<"No";}else{cout<<"Yes";}return 0;
}

判定复杂度 \(O(n+m)\)

Part2:二分图最大匹配

设 G 为二分图,若 G 的子图 M 中,任意两条边都没有公共节点,那么称 M 为二分图 G 的一组匹配。 在二分图中,包含边数最多一组匹配称为二分图的最大匹配

说人话就是,设二分图的两个集合为 \(G_1,G_2\), 两个集合之中的点互相有连边,\(G_1\) 集合中点要和 \(G_2\) 中的 与自己连了边的 点 匹配,同时,与另一个点匹配了的点不能与别的点匹配,求最大的能匹配数

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

那么,二分图的最大匹配该怎么求呢?

匈牙利算法

有一种叫匈牙利算法的算法可以在 \(O(nm)\) 的时间复杂度之内来解决这个问题

我们依次枚举 \(G_1\) 中的点 \(i\),在 \(G_2\) 中寻找他们的匹配.
我们依次遍历 在 \(G_2\) 中与 \(i\) 相连的 所有点 \(x\) ,会有两种情况

  • \(x\) 还没有与别的点匹配
    直接将 \(x\)\(i\) 匹配即可
  • \(x\) 已经与别的点匹配了
    我们设 \(x\) 原来与 \(y\) 匹配了
    那么我们 看能否 递归的让 \(y\) 重新找到一个点匹配,
    若能,则将 \(y\) 与那个点匹配,然后将 \(x\)\(i\) 匹配
    若不能,那么 \(i\) 点无法从形成匹配

形式化的理解就是原本 \(x\)\(y\) 在一对 ,现在 \(y\) 还有其他舞伴 ,就把 \(x\) 让给了 \(i\)

这样就能多出一个匹配,相当于找到一条 “增广路径”

点击查看匈牙利算法的代码
#include<bits/stdc++.h>
using namespace std;
int n,m,e;
struct edge
{int f,t;
};
edge edges[100000];
struct node
{vector<int> to;int match;bool vis;};
node nodes[100000];
bool dfs(int x)
{int to;for(int ww=0;ww<nodes[x].to.size();ww++)//遍历这个点的所有能够到达的点 {to=edges[nodes[x].to[ww]].t;//能到的点 if(nodes[to].vis)//如果要去的点被遍历过 {continue;}nodes[to].vis=1;//标记要去的点 if(nodes[to].match==0||dfs(nodes[to].match))//如果要去的点没被匹配 或者 与其匹配的点还有别的选择即还能和别的点匹配 {nodes[to].match=x;//这两个点匹配 return 1;//返回可行 }}return 0;//不可行 
}
int main()
{ios::sync_with_stdio(0);cin>>n>>m>>e;int a,b;for(int ww=1; ww<=e; ww++){cin>>a>>b;edges[ww].f=a;edges[ww].t=b;edges[ww+e].f=b;edges[ww+e].t=a;nodes[a].to.push_back(ww);nodes[b+n].to.push_back(ww+e);}int ans=0;for(int q=1;q<=n;q++){for(int yy=1;yy<=m;yy++){nodes[yy].vis=0;}if(dfs(q)){ans++;}}cout<<ans;return 0;
}

网络流做法

前置知识点: 网络流

事实上,二分图的最大匹配问题也可以转化为网络流来解决

我们假设有一个超级源点,将这个点向 \(G_1\) 中的每一个点 连一条 流量 为 \(1\) 的边,\(G_1,G_2\)之间的边的流量也为 \(1\) 然后 建立一个 超级汇点 将 \(G_2\) 中的每一个点 与超级汇点建立一条 流量 为 \(1\) 的边,二分图最大匹配的问题就被转化为了网络流的最大流问题

同时,这种做法能更好的处理一些匹配问题

若跑 EK 算法 时间复杂度 \(O(n m^2)\)
若跑 Dinic 算法 时间复杂度 \(O(\sqrt{n}m)\) ,非常优秀~

Part3:补充

二分图还有一点别的东西,比如:

二分图最小点覆盖

最小点覆盖就是要选最少的点,满足二分图的每条边至少有一个端点被选。

那么,先说结论: 最小点覆盖所需点数=最大匹配的边数

点击查看证明 将二分图点集分成左右两个集合,使得所有边的两个端点都不在一个集合。

考虑如下构造:从左侧未匹配的节点出发,按照匈牙利算法中增广路的方式走,即先走一条未匹配边,再走一条匹配边。由于已经求出了最大匹配,所以这样的「增广路」一定以匹配边结束,即增广路是不完整的。在所有经过这样「增广路」的节点上打标记。则最后构造的集合是:所有左侧未打标记的节点和所有右侧打了标记的节点。

首先,这个集合的大小等于最大匹配。左边未打标记的点都一定对应着一个匹配边(否则会以这个点为起点开始标记),右边打了标记的节点一定在一条不完整的增广路上,也会对应一个匹配边。假设存在一条匹配边左侧标记了,右侧没标记,左边的点只能是通过另一条匹配边走过来,此时左边的点有两条匹配边,不符合最大匹配的规定;假设存在一条匹配边左侧没标记,右侧标记了,那就会从右边的点沿着这条匹配边走过来,从而左侧也有标记。因此,每一条匹配的边两侧一定都有标记(在不完整的增广路上)或都没有标记,匹配边的两个节点中必然有一个被选中。

其次,这个集合是一个点覆盖。由于我们的构造方式是:所有左侧未打标记的节点和所有右侧打了标记的节点。假设存在左侧打标记且右侧没打标记的边,对于匹配边,上一段已经说明其不存在,对于非匹配边,右端点一定会由这条非匹配边经过,从而被打上标记。因此,这样的构造能够覆盖所有边。

同时,不存在更小的点覆盖。为了覆盖最大匹配的所有边,至少要有最大匹配边数的点数。

证明方式来自 OIwiki

二分图:二分图最大独立集

二分图最大独立集就是要求选最多的点,满足两两之间没有边相连。

结论:因为在最小点覆盖中,任意一条边都被至少选了一个顶点,所以对于其点集的补集,任意一条边都被至多选了一个顶点,所以不存在边连接两个点集中的点,且该点集最大。因此二分图中,\(最大独立集 = n- 最小点覆盖\)


完结撒花!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/70396.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

解密prompt系列40. LLM推理scaling Law

OpenAI的O-1出现前,其实就有大佬开始分析后面OpenAI的技术路线,其中一个方向就是从Pretrain-scaling,Post-Train-scaling向Inference Scaling的转变,这一章我们挑3篇inference-scaling相关的论文来聊聊,前两篇分别从聚合策略和搜索策略来优化广度推理,最后一篇全面的分析…

浅谈一类动态开点线段树优化 - DEST树

前言 线段树,是一种优秀的数据结构,其应用极为广泛。其中,动态开点值域线段树,配合上线段树合并,甚至能替代或超越平衡树。但是,这种线段树的树高与值域相关,很容易产生四五倍常数。无论考虑时间或空间复杂度,这样的树都不算优。那么,我们是否能想办法优化它呢? 优化…

『模拟赛』多校A层冲刺NOIP2024模拟赛05

『模拟赛记录』多校A层冲刺NOIP2024模拟赛05Rank 烂。A. 好数(number) 签,唐,没签上。 考虑之前几次类似的题方法都是选 \(k-1\) 的方案存起来以使总复杂度除以一个 \(n\),故考虑记共 \(n^2\) 个两两组合之和,枚举当前点 \(i\) 前面的点,找是否有值为它们的差的组合,复…

vscode 搭建 python 开发环境

1、安装 python 插件 2、按 Ctrl + Shift + P,在打开的输入框中输入 Python: Select Interpreter 搜索,选择 Python 解析器3、运行:ctrl + F5,调试:F5 4、查看安装包列表pip list5、安装外部库pip install xxx

idea - properties文件unicode中文显示

💖简介 idea中properties文件中文默认展示为unicode码unicode 中文展示为 \u开头的ASCII🌟调整中文显示 idea -> settings -> Editor -> File EncodingsGlobal Encoding 选择 UTF-8 Project Encoding 选择 UTF-8 Default encoding for properties files 选择 UTF-…

基于MPPT的太阳能光伏电池simulink性能仿真,对比扰动观察法,增量电导法,恒定电压法

1.课题概述在simulink中,实现基于MPPT的太阳能光伏电池,对比对比扰动观察法,增量电导法,恒定电压法三种MPPT方法。2.系统仿真结果局部放大可以看到三个算法的对比结果如下:3.核心程序与模型 版本:MATLAB2022a 4.系统原理简介太阳能光伏(PV)系统是一种将太阳能转换为电能的…

Nacos服务注册与发现的原理和如何配置

由于在大型为微服务项目中存在很多服务提供者,甚至相同的服务会使用不同的路径去调用,为了更好的管理并调用这些服务,我们需要使用注册中心来帮助我们管理这些服务以nacos为例, 1.当使用nacos来管理服务的时候,服务启动时会将自己的注册信息,例如服务名,Ip,端口注册到注…

多校 A 层冲刺 NOIP2024 模拟赛 05

难度 ★★★☆☆多校A层冲刺NOIP2024模拟赛05 T1 好数(number) 签到题 首先 \(O(nV)\) 的背包暴力是显然的,注意到本题只需要合法性,状态只有 \(0/1\),上 \(bitset\) 优化转移即可。 时间复杂度 \(O(\frac{nV}{w})\)。 T2 SOS字符串(sos) 签到题 计数题难点在不重不漏,…