人员分配(最大匹配)

人员分配

Description

设有M个工人x1, x2, …, xm,和N项工作y1, y2, …, yn,规定每个工人至多做一项工作,而每项工作至多分配一名工人去做。由于种种原因,每个工人只能胜任其中的一项或几项工作。问应怎样分配才能使尽可能多的工人分配到他胜任的工作。这个问题称为人员分配问题。

Input

第一行两个整数m,n分别为工人数和工作数。
接下来一个整数s,为二分图的边数。
接下来s行,每行两个数ai,bi表示第ai个工人能胜任第bi份工作

Output

一个整数,表示最多能让多少个工人派到自己的胜任的工作上。

Sample Input

3 3
4
1 2
2 1
3 3
1 3

Sample Output

3

Hint

规模:
1<=m,n<=100
1<=s<=10000

解题思路

这题就是一道最大匹配
匈牙利算法网络流的模板题
我初学,只学了匈牙利算法
所以只写了匈牙利算法(抱歉)

它的基本思想是:对于已知的匹配M,从X中的任一选定的M非饱和点出发,用标号法寻找M增广链。如果找到M增广链,则M就可以得到增广;否则从X中另一个M非饱和点出发,继续寻找M增广链。重复这个过程直到G中不存在增广链结束,此时的匹配就是G的最大匹配。这个算法通常称为匈牙利算法,因为这里介绍的寻找增广链的标号方法是由匈牙科学者Egerváry最早提出来的。
由增广路的定义可以推出下述三个结论:
1-P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
2-P经过取反操作可以得到一个更大的匹配M’。
3-M为G的最大匹配当且仅当不存在相对于M的增广路径。

在这里插入图片描述算法轮廓:
(1)置M为空
(2)找出一条增广路径P,通过取反操作获得更大的匹配M’代替M
(3)重复(2)操作直到找不出增广路径为止

匈牙利算法

上面的图为 :二分图

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,{R})是一个无向图。如顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属两个不同的子集。则称图G为二分图。
在这里插入图片描述二分图

AC代码

邻接矩阵

邻接矩阵就很简单了,用一个a来存相连的边,再套用匈牙利算法就AC了

#include<iostream>
#include<cstdio>
using namespace std;
int m,n,s,x,y,answer,cover[105],father[105],a[105][105];
bool dfs(int x)//dfs
{
	if(x==0)return true;//特判,节省时间
	for(int i=1;i<=n;i++)
	 if(a[x][i]==1&&cover[i]==0)//如果有边,且未被标记
	 {
	 	cover[i]=1;//标记
	 	int sum=father[i];//在dfs内int,不能在外面int,否则sum会成为全局变量
		father[i]=x;//更改
	 	if(sum==0||dfs(sum))return true;
	 	father[i]=sum;//回溯
	 }
	return false;//返回
}
int main()
{
	scanf("%d%d",&m,&n);//输入
	scanf("%d",&s);
	for(int i=1;i<=s;i++)
	{
		scanf("%d%d",&x,&y);
		a[x][y]=1;//相连边标记
	}
	for(int i=1;i<=m;i++)//每个点都枚举一次
	{
		memset(cover,0,sizeof(cover));//清零
		dfs(i);
	}
	for(int i=1;i<=n;i++)//累加
	 if(father[i]!=0)answer++;
	printf("%d",answer);//输出
	return 0; 
}

邻接表

邻接表就和邻接矩阵的方法差不多,同样用一个a来存储相连的边,只不过存储的方式不同,再套用匈牙利算法就AC了
邻接表(不会的请进)

#include<iostream>
#include<cstdio>
using namespace std;
int m,n,s,x,y,tot,answer,head[105],cover[105],father[105];
struct node//结构体
{
	int to,next;
}a[10005];
void add(int x,int y)//邻接表
{
	a[++tot]=(node){y,head[x]};
	head[x]=tot;
}
bool dfs(int x)//dfs
{
	if(x==0)return true;//特判,节省时间
	for(int i=head[x];i;i=a[i].next)//枚举相连的边
	 if(cover[a[i].to]==0)//如果没有被标记
	 {
	 	cover[a[i].to]=1;//标记
	 	int sum=father[a[i].to];//在dfs里面int,在外面int会变成全局变量
		father[a[i].to]=x;//更改
	 	if(sum==0||dfs(sum))return true;
	 	father[a[i].to]=sum;//回溯
	 }
	return false;//返回
}
int main()
{
	scanf("%d%d",&m,&n);//输入
	scanf("%d",&s);
	for(int i=1;i<=s;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);//建表
	}
	for(int i=1;i<=m;i++)//枚举每一个工人
	{
		memset(cover,0,sizeof(cover));//清零
		dfs(i);
	}
	for(int i=1;i<=n;i++)//累加
	 if(father[i]!=0)answer++;
	printf("%d",answer);
	return 0; 
}

谢谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值