ACM二分匹配BipartiteMatching

二分图

一个图的所有顶点可以分为两个集合X和Y

eg. 婚配问题:所有边一端在男,一端在女


最大匹配

匈牙利算法

基本思想

        (想象男女婚配问题,男生在左,女生在右)先看左侧的男生,对于第一个男生,在右边查找他所能匹配的第一个女生,并且连接。如此往复。假如出现下面的情况:一个男生(记为男1)发现他所能匹配的第一个女生(记为女1)已经有匹配对象(记为男2)了,那么就以女1的匹配对象(男2)为研究对象,看男2除了当下的这个匹配对象以外,接下来还有没有可以匹配的对象,假如下一个女生(记为女2)也有了匹配对象,那么就递归下去(以女2的匹配对象(记为男3)为研究对象……),假如没有,那么就把他们连接起来。每一次dfs操作假如能够成功,那么匹配数就+1。

        所以一共做N次dfs。


二分图的最小顶点覆盖

基本概念

求二分图中最少有几个点,使得每条边都和至少一个点关联。(用最少的点覆盖边)

基本思想

        结果就和求解最大匹配数一样。因为当删除最大匹配的所有点之后,其他所有的点之间的关系都断掉了,那么就说明所有的边都和这些点有关系。

经典例题

机器任务安排

        解:机器A的模式作为左边的点,机器B的模式作为右边的点。假如一个任务既可以在A上完成,又可以在B上完成,那么就在这两个对应的点当中画一条边。所以这个问题就等效于求解最小顶点覆盖问题。注意A、B最开始都处于0号模式。

#include <cstdio>
#include <cstring>
using namespace std;

int uNum, vNum, k, linker[100];    //link用来存右点的对象,下表表示第几个右点
bool graph[100][100], visit[100];    //邻接矩阵与标记数组(标记每一次搜索中右点是否访问过)
int hungary();
bool dps(int);

int main()
{
	while(scanf("%d", &uNum), uNum)
	{
		int u, v, id;
		scanf("%d%d", &vNum, &k);
		memset(graph, false, sizeof(graph));
		while(k--)
		{
			scanf("%d%d%d", &id, &u, &v);
			if(u != 0 && v != 0)    //不需要考虑需要机器A、B模式为0的情况
				graph[u][v] = true;
		}
		printf("%d\n", hungary());
	}
	return 0;
}

int hungary()
{
	int res = 0;
	memset(linker, -1, sizeof(linker));
	for(int u = 1; u < uNum; u++)    //做n次dfs,dfs返回true就表明这一次可以找到匹配
	{
		memset(visit, false, sizeof(visit));
		if(dps(u)) res++;
	}
	return res;
}

bool dps(int u)
{
	for(int v = 1; v < vNum; v++)
		if(graph[u][v] && !visit[v])    //如果有联系而且右点这一趟有没有考虑过
		{
			visit[v] = true;
			if(linker[v] == -1 || dps(linker[v]))    //假如没有配对过(直接匹配成功)或者它的配对对象可以再找到另一个新的配对对象
			{
				linker[v] = u;
				return true;
			}
		}
	return false;    //找不到
}

DAG图的最小路径覆盖

DAG图:有向无环图

最小路径覆盖:用最少的路径覆盖所有的点

基本思想

        将每一个点复制一份,把一个点列变成两个相同的点列,强行转换成二分图。因为每次找到一个匹配(想想男女配对中的匈牙利算法),都会减少一条需要走的路径(以下面伞兵的例子来说就是:每次找到一个匹配都会让同一个伞兵多走一段路,因此就可以少用一个伞兵)。所以最小路径数就等于顶点数减去最大匹配数(二分图的最大独立集

经典例题

空袭

#include <cstdio>
#include <cstring>
using namespace std;

int uNum, vNum, k, linker[501], testNum;
bool graph[501][501], visit[501];
int hungary();
bool dps(int);

int main()
{
	scanf("%d", &testNum);
	while(testNum--)
	{
		scanf("%d", &uNum);
		vNum = uNum;
		scanf("%d", &k);
		int u, v;
		memset(graph, false, sizeof(graph));
		while(k--)
		{
			scanf("%d%d", &u, &v);
			if(u != 0 && v != 0)
				graph[u][v] = true;
		}
		printf("%d\n", uNum-hungary());
	}
	return 0;
}

int hungary()
{
	int res = 0;
	memset(linker, -1, sizeof(linker));
	for(int u = 1; u <= uNum; u++)
	{
		memset(visit, false, sizeof(visit));
		if(dps(u)) res++;
	}
	return res;
}

bool dps(int u)
{
	for(int v = 1; v <= vNum; v++)
		if(graph[u][v] && !visit[v])
		{
			visit[v] = true;
			if(linker[v] == -1 || dps(linker[v]))
			{
				linker[v] = u;
				return true;
			}
		}
	return false;
}
  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值