【算法详解】 二分图及其匹配

一、二分图基础


  • 什么是二分图?

如果一个无向图可以分为两个不同的集合 A , B A,B A,B, 使得集合 A , B A,B A,B内部的点中没有边相连,只存在 A A A集合中的点连向 B B B集合中的点或者相反。
这样的图称为二分图

  • 无向图上的基本术语/概念

一、二分图的匹配:选取二分图中的边集 S S S,当且仅当集合中的任意两条边都没有共同公共端点时,S为二分图的一个匹配
二、匹配边/未匹配边:在集合S中的边称为匹配边,除此之外的边称为未匹配边
三、未盖点(未匹配点)/匹配点:匹配边相连的端点称为匹配点,除此之外的称为未盖点(未匹配点)
四、交错路(增广路):任意两条相邻的边一定是一条匹配边另一条为匹配边的路称为交错路
五、可增广路:端点为未盖点的交错路(增光路)称为可增广路
六、可增广路的性质

  • 长度 l e n len len为奇数(两端都要是非匹配点)
  • 1 , 3 , 5 , … … l e n 1,3,5,……len 1,35len条边都是非匹配边,而 2 , 4 , 6 … … l e n − 1 2,4,6……len-1 2,4,6len1都是匹配边,也就是说,我们只要把状态反一反,就可以使匹配数+1
  • 所以我们得到以下结论:若二分图的匹配S使最大匹配,当且仅当S不是增广路

二、二分图的最大匹配

  • 二分图匹配其实就是找增广路的个数然后将他变成可增广路使得匹配数+1

这里需要介绍匈牙利算法(增广路算法):
1、设集合 S S S为空,即此时一条匹配边都没有
2、找出图中的增广路,将边取反,即匹配边变成非匹配边,非匹配边变成匹配边,此时匹配个数便会在原基础上+1
那么这里的关键就是如何求增广路。
当我们想匹配(x,y)时,满足以下条件之一的时候,易证找出了一条增广路:
1、y本身便是非匹配点,此时直接可以连边组成长度为1的增广路
2、y已经连了 x ′ x' x,但 x ′ x' x可以匹配到另一个新的点 y ′ y' y,那么此时便可以让 x ′ x' x去匹配 y ′ y' y, y y y x x x便可以匹配
d f s dfs dfs便可解决。

但匈牙利更是一个基于贪心的算法,它认为:当一个点已经成为匹配点时,只会找到增广路使总的匹配数增多,而不会使当前点成为非匹配点。

显然是正确的(其实我也不是到为什么是正确的

代码实现:

bool dfs(int x){
	for (int i=linkk[x];i;i=e[i].Next){
		int y = e[i].y;
		if (vis[y]) continue;
		vis[y] = 1;
		if (!match[y] || dfs(match[y])) {
		    match[y] = x;match[x] = y;return 1;
		}
	}
	return 0;

    for (int i=1;i<=n;i++) memset(vis,0,sizeof vis),ans+=dfs(i);
}

三、例题分析

在这里插入图片描述


Solution

  • 首先,这个图是二分图
  • 我们可以将这个图上的点分为两类:一类是不必要匹配的点,另一类是必须匹配的点
  • 二不必要匹配的点分为未匹配的点和可匹配的点
  • 我们得出一个结论:当当前点是未匹配的点的时候,一定是大象赢,当当前点是必须匹配的点的时候,一定是老鼠赢
  • 证明:
    若当前点是不必匹配点,那么他一定可以找到一个已匹配点,同样,这个已匹配点一定可以找到一个不必匹配点……以此往复,最后结束一定是在一个不必匹配点,那么先手就输了
    若当前点是一个必匹配点,那么以他连出去一定能找到另一个必匹配点,而这个必匹配点只能找到不必匹配点,此时问题就转化成了如上的情况,只是先后手换了一换,先手则胜

Code

#include<bits/stdc++.h>
using namespace std;
int n,m;
struct node{
	int y,Next;
}e[10101000];
int linkk[1010100];
bool vis[3010];
int match[3010];
int cant;
int len = 0;

void insert(int x,int y){
	e[++len].Next = linkk[x];
	linkk[x] = len;
	e[len].y = y;
}

bool dfs(int x){
	for (int i=linkk[x];i;i=e[i].Next){
		int y = e[i].y;
		if (vis[y] || y == cant) continue;
		vis[y] = 1;
		if (!match[y] || dfs(match[y])) {
		    match[y] = x;match[x] = y;return 1;
		}
	}
	return 0;
}

int main(){
	freopen("bigraph.in","r",stdin);
	freopen("bigraph.out","w",stdout);
	scanf("%d %d",&n,&m);
	for (int i=1;i<=m;i++){
		int x,y;scanf("%d %d",&x,&y);
		insert(x,y),insert(y,x);
	}
	for (int i=1;i<=n;i++)
	  if (!match[i]) memset(vis,0,sizeof vis),dfs(i);
//	for (int i=1;i<=n;i++) cout<<match[i]<<' ';
//	cout<<endl;
	for (int i=1;i<=n;i++){
		if (!match[i]) printf("Elephant\n");
		else{
			memset(vis,0,sizeof vis);
			int x = match[i];
			match[i] = match[x] = 0;
			cant = i;
			if (dfs(x))  printf("Elephant\n");
			else match[i] = x , match[x] = i , printf("Hamster\n");
		}
	}
	return 0;
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值