二分图

二分图

前情提要:今日速查打二分图最大匹配,发现自己的匈牙利算法《学的非常好》,于是一怒之下写了这篇笔记

1.什么是二分图?

若一张无向图GN个节点可分成AB两个不相交的非空集合,并且同一集合内的点之间没有边相连,那称该图为二分图。

性质:二分图中不存在奇环(一个点想回到自己的点集肯定会经过奇数条边)

2.二分图判定

1)染色法

从一个节点u染色,并把与其相连的所有点染上异色(然后再从这些点出发去check,此过程用递归实现),若最后发现存在一个点被染上了两种不同颜色,则证明该图不是二分图,否则一定是二分图。

实现:dfs

#include<bits/stdc++.h>
using namespace std;
#define F(i,l,r) for(int i=l;i<=r;++i)
const int N=205,M=5005;
int n,m,u,v,col[N];
vector<int> G[N];
inline bool dfs(int u,int c){
	col[u]=c;
	for(auto v:G[u]){
		if(col[v] && col[v]==c) return 0;
		if(!col[v] && !dfs(v,3-c)) return 0;
	}
	return 1;
}
int main(){
	scanf("%d%d",&n,&m);
	F(i,1,m){
		scanf("%d %d",&u,&v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	F(i,1,n) if(!col[i] && !dfs(i,1)) { puts("NO"); return 0;}//要从每个点都开跑一遍
	puts("YES");
	return 0; 
}

2)扩展域并查集

扩展域并查集用以解决多个逻辑冲突问题:有两个集合,naibi, m 个形如 (ai,bi) 的条件表示 aibi 不在同一集合中,问最终能否达到。

思想:约束a,b不在一个集合中即就是将a¬b放入放入同一集合内,再将b¬a放入同一集合内,最后检查a¬a,b¬b是否在同一集合中即可,实际编程中merge(a,¬b)就是merge(a,b+n).

对于这道题而言可以先通过swap保证 u<v, 然后就只需要 merge(u,v+n) 即可(因为只要保证有一对不满足条件的点就能完成判定)。

#include<bits/stdc++.h>
using namespace std;
#define F(i,l,r) for(int i=l;i<=r;++i)
const int N=205,M=5005;
int n,m,u,v,fa[N<<1];//并查集开两倍空间 
inline int getfather(int x){
	if(fa[x]!=x) fa[x]=getfather(fa[x]);
	return fa[x];
}
inline void add(int x,int y){
	x=getfather(x),y=getfather(y);
	if(x!=y) fa[x]=y; 
}
inline bool chk(int x,int y){ return getfather(x)==getfather(y); }
int main(){
	scanf("%d%d",&n,&m); F(i,1,n*2) fa[i]=i;
	F(i,1,m) {
		scanf("%d%d",&u,&v); ++u,++v;	
		if(u>v) swap(u,v);
		add(u,v+n);
	}
	F(i,1,n) if(chk(i,i+n)) return puts("NO"),0;
	puts("YES");
	return 0;
}

二分图最大匹配

对于G的子图M,若M中任意两边都没有公共端点,则称M是二分图的一种匹配,所有匹配中包含边数最多的一组匹配称为二分图的最大匹配。

1)匈牙利算法:

思想:通过不断找增广路寻找更大的匹配,若无法找到增广路了,当前匹配就是最大匹配。

交替路:非匹配边--->匹配边--->非匹配边--->匹配边--->非匹配边--->匹配边--->......

增广路:从非匹配点出发沿交替路到达另一非匹配点的路径。

性质:增广路上的非匹配边总比匹配边多一条(两两为一组,最后一条非匹边会剩下),故 非匹 与 已匹 互换身份就能使匹配变大。

实现:dfs(参考代码)或bfs.
注意连边只从左集连到右集即可,认真体会vis的作用(对每个点尝试找匹配时,每个点只能尝试一次)。
例题

#include<bits/stdc++.h>
#define F(i,l,r) for(int i=l;i<=r;++i)
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
const int N=505;
int k,m,n,pi[N],first[N],idx=0,sum=0;
bool vis[N];
struct node{ int v,ne; }e[N<<1];
inline void add(int x,int y){ e[++idx]=(node){y,first[x]}; first[x]=idx; }
inline bool dfs(int u){
	for(int i=first[u];i;i=e[i].ne){
		int v=e[i].v; if(vis[v]) continue; vis[v]=1;
		//右集中的点不重复访问(每一轮每个右集点只会更换匹配一次)
		if(!pi[v] || dfs(pi[v])) return pi[v]=u,1;
	} return 0;
}
int main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin>>k;
	while(k){
		mem(pi),mem(first),idx=0,sum=0;
		cin>>m>>n;
		F(i,1,k){
			int u,v;
			cin>>u>>v; 
			add(u,v); //女生去匹配男生,故如是连边(只连单向边,左集连到右集)
		}
		F(i,1,m){//枚举女生(左集) 
			mem(vis);
			if(dfs(i)) ++sum;
		}
		cout<<sum<<'\n';
		cin>>k;
	}	
}

最坏情况下以每个点为起点寻找增广路会遍历整张图每条边(continue也会耗时所以注意不是O(N)),时间复杂度为O(M),故总时间复杂度为O(NM).

2)Dinic算法:学了再说

再忘我就再写,我还不信了这么简单个算法我能记不住?

posted @   superl61  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示