浅谈二分图
二分图定义
顾名思义,二分图就是把图分成两份,怎么分成两份呢?
很简单,将图的点分成两个集合,让每个集合的点没有边相连
知道了定义,就要学会如何判定一个图是不是二分图
二分图的判定
容易证明,如果一个图没有奇环,那么这个图一定是二分图
那么如何判定一个图是否有奇环呢?
染色法
带你深度解析染色法
我们染色法的目的就是将每一个连通块的点用两个颜色标记起来
做到了就是二分图
如果做不到那么就不是二分图
那做不到的意思是什么呢?
当染色的图中有冲突时,那么就是做不到了
冲突是什么意思呢?
我们对于一个有奇环的东东进行各种玄学操作
如下:
1.选定一个点
2.将它染色,并将所有的与之相连的点染成另一个颜色
3.对于与之相连的点向染色旁边扩散开来
如果有冲突,即对于一个点要染成两个颜色就洗白
结合下面的图理解理解
然后你就学会了二分图的判定
尝试自己写写代码
代码如下:
#include<bits/stdc++.h>
#define N 100000
using namespace std;
vector<int>to[N];
int n,m;
int color[N];
void build(int x,int y){
to[x].push_back(y);
to[y].push_back(x);
}
void pre( ){
cin>>n>>m;
int x,y,z;
while(m--){
cin>>x>>y;
build(x,y);
}
}
bool dfs(int k,int c){
vector<int>::iterator i;
color[k]=c; //染色
for(i=to[k].begin( );i!=to[k].end( );++i){
if(color[*i]==0)if(!dfs(*i,3-c))return 0; //处理相邻点
else if(color[*i]==c)return 0; //冲突凉凉
}
return 1;
}
int main( ){
std::ios::sync_with_stdio(0);
pre( );
int i;
for(i=1;i<=n;i++)
if(!color[i]&&!dfs(i,1))goto GG;
puts("Yes");
return 0;
GG:
puts("No");
return 0;
}
好了二分图的判定就告一段落了
二分图的玄学东东
在接下来的学习之前,我们要先了解一些令人鹅心的名词
先上一个图:
匹配:
首先,匹配就是一堆边的集合
但是要保证这些边没有一个公共点
那么就有一下一堆堆匹配:
1.{1,6},{2,7},{3,5}
2.{1,6},{3,5}
\(\cdots\)
注意,空集也为一个匹配
引申:
1.含有边数最多的就是最大匹配
2.所有点都是匹配点那么就是完全匹配
3.完全匹配一定是最大匹配,想一想为什么
非匹配边&匹配边
首先,这个有趣的概念是对于一个匹配来说的
匹配边包含在这组匹配中的边
非匹配边相反
比如对于匹配={1,6},{3,5}
则{1,6},{3,5}为匹配边,其它的就是非匹配边
非匹配点&匹配点
非匹配点,顾名思义,就是不在匹配边上的点
匹配点则是在匹配边上的点
增广路径
增广路径,是对于一个匹配来说的
增广路径,就是连接两个非匹配点的路径
而增广路径,是匹配边与非匹配边交替的一条路径
比如说在匹配{2,7}
那么就存在一个连接{4,8}的增广路径是4->7->2->8
增广路径有一条有趣的性质
那就是把路径上的边,匹配边变为非匹配边,非匹配边变为匹配边
那么匹配边的数量就+1
那么这也可以推出:如果一个匹配没有增广路径,那么这匹配就是这个二分图的最大匹配
二分图的些许操作
最大匹配
最大匹配是啥子鬼?
最大匹配就是找到一个匹配,使边最多
最大匹配咋搞?
我们前面介绍了一个性质:把一个增广路径全部取反,那么匹配的边数就+1
所以最大匹配的核心就是找到增广路径
咋找增广路径呢?
匈(找)牙(女)利(友)算法
匈(找)牙(女)利(友)算法
官方解释匈牙利算法
我们的目标就是不断地找增广路径,不断地取反
增广路径是啥?是连接两个非匹配点的家伙
那么我们就从一个非匹配点出发去找一个匹配点连接
再从那个匹配点的原本的匹配点找到另一个点(不是它原本的匹配点),如果是非匹配点就意味找到了
图5图6 点右3应该是红的,画错了
结合下面理解一下
红色点是匹配点
红色边是匹配边
分色线是增广路径的非匹配边
增广路径
取反
同理
增广路径
取反
增广路径
取反
玄学解释找女友算法
其实找女友算法就是匈牙利算法
不过有一些有趣的解释
匹配点就是有对象的
非匹配点就是单身狗
比如找增广路径就是有小三插入
等等
然后你又学会了二分图的最大匹配
代码
#include<bits/stdc++.h>
#define N 100100
using namespace std;
vector<int>to[N];
int match[N];
bool vis[N];
int n,m;
bool dfs(int now){
vector<int>::iterator i;
vis[now]=1; //标记
for(i=to[now].begin( );i!=to[now].end( );i++)
if(!vis[*i]){ //如果没有处理过
vis[*i]=1; //标记处理
if(!match[*i]||dfs(match[*i])){ //如果是单身狗或者他原本的女友可以另有新欢
match[*i]=now; //重新耍朋友
match[now]=*i;
return 1;
}
}
return 0;
}
int ans;
void build(int x,int y){
to[x].push_back(y);
to[y].push_back(x);
}
int main( ){
std::ios::sync_with_stdio(0);
cin>>n>>m;
int x,y;
while(m--){
cin>>x>>y;
build(x,y);
}
int i;
for(i=1;i<=n;i++)
if(!match[i]){ //如果没对象
memset(vis,0,sizeof(vis));
if(dfs(i))ans++; //找到就取反,边数+1
}
cout<<ans<<endl;
}
下一个就去学学带权二分图吧