今天本来想整理

让我们回到第一个情景:国家主席布里jiojio邓布利多先生对于相亲大会的成果并不满意,在你向他说明情况后,他决定修改本国宪法

:“其实吧,\(5\)号人也挺好的。”然后\(3\)号去找\(5\)号,发现\(5\)号没有男朋友,于是\(3\)号和\(5\)号匹配,把\(1\)号让了出来

因为\(1\)号被\(3\)号让了出来,所以\(4\)号和\(1\)号成功匹配
(算法竞赛)简单易懂二分图-LMLPHP
最后一个男嘉宾\(6\)号:“我喜欢\(1\)号!”于是你们去找\(1\)号,\(1\)号有男朋友是\(4\)号

老套路,你去问\(4\)号能不能换,把\(1\)号让出来给\(6\)号,可是\(4\)号除了和\(1\)号之外没有边了,他说:“不换不换!老子不换!说什么也不换!”

然而\(6\)号也只喜欢\(1\)号,但是因为先到先得,你只能遗憾的告诉\(6\)号他得孤独终老了
(算法竞赛)简单易懂二分图-LMLPHP

现在笔者告诉你,刚才你所完成的,就是匈牙利算法的全过程了,这张二分图的最大匹配数就是\(3\)

这里写代码的时候注意如果遇到更换的情况,新换的那个点不可以是需要让出来的那个点

对于以上繁杂的过程,笔者用了一个更加违反人类伦理的话来总结:

好啦不扯皮了,相信经过笔者的一通比喻,大家已经明白了匈牙利算法的实现原理了,下面来看代码吧!

这里假设我们已经对二分图染好色并存在了\(col\)数组里

bool dfs(const int x){
//match[i]存右边i号点的匹配是左边的哪个点
	for(unsigned int i=0;i<v[x].size();i++){//扫描x的所有出边
		int y=v[x][i];
		if(vis[y]==0){//如果扫描到的这个点不是需要让出来的那个点
			vis[y]=1;//标记y点不能被需要更换的情况作为替换点
			if(match[y]==-1||dfs(match[y])){ //match[y]==-1说明这个右边点没有匹配过,dfs(match[y])如果为真说明可以换
				match[y]=x;//现在y和x匹配
				return true;//返回真
			}
		}
	}
	return false;//如果不满足上面那一大串的条件,说明换不了,返回假
}
inline int APath(){//尝试匹配
	int res=0;
	for(int u=0;u<n;u++){//对于所有的点,我们只需要对黑点或白点尝试匹配即可
		memset(vis,0,sizeof(vis));
		if(col[u]==0){//如果是黑点就尝试匹配
			if(dfs(u)) res++;//匹配成功
		}
	}
	return res;//返回值为最大匹配数
}

Part 4:例题

学了这么牛B(哲学)的算法,怎么能不拿出来练练手呢?

传送门:https://www.luogu.com.cn/problem/P6268

题目中给出了\(m\)条边,被边连接的两个点种只能选一个,求最多选几个点

很容易就看出来本题是求最大独立集的一道题目,由于题目中给出的只是边连接的两个点,并不知道左还是右,所以我们要先染色

染好色后(本题的数据保证是二分图了,所以不用特判)我们直接跑一遍匈牙利,然后\(n-\)最大匹配数即可

然而交上去只有\(60\)分,不知所措的我\(De\)了一下午\(bug\),然并卵(不让下数据蛋疼),最后愤怒的我抄了个题解下来去对拍

发现:我在记录右部节点的匹配点时,默认\(match[y]==0\)是没有匹配,但是,\(TND\)这个题有个\(0\)号点,如果\(0\)号点已经和\(y\)匹配上了,查询的时候还是默认\(y\)没有匹配过,导致找到了错的增广路,然后发现匹配数比真实的答案要大\(1\),然后\(WA\)掉了……

所以为了避免类似的惨剧发生,这里强烈建议大家把\(match\)初始化成\(-1\),避免踩坑

\(Code\)

#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<algorithm>
#include<set>
#include<map>
#include<utility>
#include<iostream>
#include<list>
#include<ctime>
#include<cmath>
#include<cstdlib>
#include<iomanip>
typedef long long int ll;
inline int read(){
	int fh=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){ if(ch=='-') fh=-1;ch=getchar(); }
	while('0'<=ch&&ch<='9'){ x=(x<<3)+(x<<1)+ch-'0';ch=getchar(); }
	return fh*x;
}
inline int _abs(const int x){ return x>=0?x:-x; }
inline int _max(const int x,const int y){ return x>=y?x:y; }
inline int _min(const int x,const int y){ return x<=y?x:y; }
inline int _gcd(const int x,const int y){ return y?_gcd(y,x%y):x; }
inline int _lcm(const int x,const int y){ return x*y/_gcd(x,y); }
const int maxn=5005;
std::vector<int>v[maxn];
int match[maxn],m,n,col[maxn];//col存每个点的颜色
bool vis[maxn];
bool dfs(const int x){
	for(unsigned int i=0;i<v[x].size();i++){
		int y=v[x][i];
		if(vis[y]==0){
			vis[y]=1;
			if(match[y]==-1||dfs(match[y])){
				match[y]=x;
				return true;
			}
		}
	}
	return false;
}
inline int APath(){//求增广路
	int res=0;
	for(int u=0;u<n;u++){
		memset(vis,0,sizeof(vis));
		if(col[u]==0){//如果是黑点就寻找增广路
			if(dfs(u))res++;//匹配成功
		}
	}
	return res;
}
void paint(const int x,const int color){
	int y;
	col[x]=color;
	for(unsigned int i=0;i<v[x].size();i++){
		if(col[y=v[x][i]]==-1){//没有被染色
			if(color==0) paint(y,1);//1表示白色
			else paint(y,0);//0表示黑色
		}
	}
}
int main(){
//	freopen("data.in","r",stdin);
//	freopen("sol.out","w",stdout);
	memset(match,-1,sizeof(match));
	n=read(),m=read();
	for(int i=0,x,y;i<m;i++){
		x=read(),y=read();
		if(x==y) continue;
		v[x].push_back(y);
		v[y].push_back(x);
	}
	memset(col,-1,sizeof(col));//一开始都没有被染色
	for(int i=0;i<n;i++)
		if(col[i]==-1) paint(i,0);//如果没有被染色,默认为黑色
	int path=APath();
	printf("%d\n",n-path);
	return 0;
}

好了今天关于二分图和增广路的分享就到这里了,感谢您的阅读,如果觉得笔者写的不错的话,请高抬贵手来个三连,谢谢您的支持!!!

08-16 01:59