二分图及应用

1.什么是二分图?

如果一个图的顶点可以分为两个集合XY,图的所有边一定是有一个顶点属于集合X,另一个顶点属于集合Y,则称该图为“二分图”或二部图Bipartite Graph

2.求最大匹配

什么是最大匹配?可以类比婚配问题,已知若干对男女之间存在可能成为夫妻的关系,选择一种组合方法,使得结婚的人数最多,就是求最大匹配。可以发现,左右两边的点是不能重复使用的。

3.匈牙利算法(Hungary)

匈牙利算法是用来求最大匹配数的常用方法。它的基本思路是:先找到一对可以匹配的左右点,将其相连,匹配成功。我们显然需要使得匹配的个数越多越好,而匈牙利算法可以使得每步结束后,匹配的个数都增加一个。它的做法是:假设二部图左边为a1,a2,a3,右边为b1,b2,b3,已知可以匹配的线路是a1和b1,a1和b2,a1和b3,a2和b1,a3和b2。首先从头开始,发现a1和b1可以匹配,便相连,此时匹配数为1;接下来为a2找匹配,发现a2和b1可以匹配,那a2和b1能不能相连呢?一般情况下,b1有两种情况,已经连了或者没连。如果b1没连其它点,那么显然应该连接a2和b1,如此便使匹配数加一,进行下一步;但在此情况下,b1已经与a1相连,我们便需要考虑,a1能不能找到一个能与a1匹配的bi点,bi点未连接(如果未连接就连接上,进行下一步)或者bi点已连接的点aj能不能找到一个能与aj匹配的bk点,……(可以发现是相同的寻找过程,如果找得到,就可以连接,而且匹配数必加一,因为这个连接抢了别人的一个连接,自己连接上了,等于不变,但是那个循环寻找的结束也会多成功一个连接,而这个连接是原先没有的)。在本具体问题中,可以发现a1可以匹配的b2,是未连接的,所以连接a1b2,并连接a2b1,如此便匹配数加1;下一步给a3找匹配,发现b2可与之匹配,但b2已与a1相连,那么我们便看a1能不能找到,发现a1能与未连接的a3匹配,于是便可以连接,即连接a3b2,a1b3,再加上原先的a2b1,匹配数又加了1。于是最大匹配数是3。

4.算法的代码实现

实现思路:寻找的过程写一个递归函数,寻找顺序从1到n即可。

下面的是寻找函数的一种写法。

int find(int x)
{
	for (int i = 1; i <= n; i++)/*遍历右边的点*/
	{
		if (vis[i] == 0 && con[x][i] == 1)
			/*vis判断右边的点在本轮中是否访问过了,可以减少一些运算量*/
			/*con储存的是可以匹配的点的对应关系,点x和y能匹配,则con[x][y]=1*/
		{
			vis[i] = 1;
			if (link[i] == -1 || find(link[i]))
				/*link表示该点有没有被连接,-1表示没有,储存的连接的点的信息*/
				/*find(link[i])就是进行下一步寻找*/
			{
				link[i] = x;/*找到了,连接上*/
				return 1;
			}
		}
	}
	return 0;/*没找到,循环continue*/
}

总体写法:

#include<bits/stdc++.h>
using namespace std;
/*求最少顶点覆盖数就是求最大匹配数*/
int con[510][510];/*连接关系*/
int vis[510];/*右边点使用与否*/
int link[510];/*右边点连接的左边点*/
int n, m, k;/*如题意*/
int dfs(int x)/*用以判断找不找得到结尾为之前未连的边的情况*/
{
	for (int y = 0; y < m; y++) {
		if (!vis[y] && con[x][y]) /*代表右边这个点在此轮没有使用过且可有连线*/
		{
			vis[y] = 1;/*标注已使用*/
			if (dfs(link[y])||link[y]==-1) /*代表要么这个点并未连过或者它的连线的点能找到另一个有未连的边的点……*/
			{
				link[y] = x;/*找到就重新连接*/
				return 1;
			}
		}
	}
	return 0;
}
int hungary()
{
	int res = 0;
	memset(link, -1, sizeof(link));/*注意连线是需要存储的,不然下一步就没有方向了*/
	for (int x = 0; x < n; x++) {
		memset(vis, 0, sizeof(vis));/*注意这是每轮清空,表示需要抢之前已经匹配的*/
		if (dfs(x))res++;/*成功一次多一个匹配数*/
	}
	return res;
}
int main()
{
	while (scanf("%d", &n), n) {
		scanf("%d%d", &m, &k);
		int id, x, y;
		memset(con, 0, sizeof(con));
		for (int i = 0; i < k; i++) {
			scanf("%d%d%d", &id, &x, &y);
			if (x && y)con[x][y] = 1;/*左右均为0时为初始情况,不产生影响*/
		}
		printf("%d\n", hungary());
	}
	return 0;
}

5.代码需要注意的点

各个数组memset的位置;开的数组的大小;对应题意,改变循环是0~n-1还是1~n;……

6.主要问题

(1)用匈牙利算法求最大匹配(ans)。

(2)最小顶点覆盖:就是求能覆盖所有边的最少顶点数,即为ans。

(3)最小路径覆盖:即求能覆盖所有点的最少的不相交路径(因为最大匹配里点就是不能重合的),为n-ans(每匹配一次,减少一条路径)。

写题最难的是看出这个题可以用匈牙利算法。

7.例题

Machine Schedule

棋盘游戏

50 years, 50 colors

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值