1338 最大匹配 人员分配(模板题)最大匹配初步解析

题目

设有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

最大匹配初步解析

第一题最大匹配,好激动啊!
这道是练手的模板题。。。
换一个背景,有n朵花,m片叶子,每朵花有它可以搭配的叶子,且每朵花只能搭配一片叶子,求最多能让多少朵花分配到与自己相配的叶子。
3 3
4
1 2
2 1
3 2
1 3
这里写图片描述
用cover[i]表示每片叶子是否已被匹配过,link[i]表示每片叶子与之匹配的花的编号
先从第一朵花做起,发现第一朵花与第二片叶子匹配,则cover[2]标记,link[2]记为1,表现在图上,就是:
这里写图片描述
第一朵花匹配好了,就往下,轮到第二朵花,发现第二朵花与第一片叶子匹配,且第一片叶子没有与之匹配的花,则cover[1]标记,link[1]记为2,表示第一片叶子与第二朵花匹配了,表现在图上,就是:
这里写图片描述
接下来轮到第三朵花,发现第三朵花可以与第二片叶子匹配,但是第二片叶子已经名“花”有主了,注意,这时就先取消掉第一朵花与第二片叶子的匹配,并且把第三朵花与第二片叶子匹配,如图:
这里写图片描述
然后尝试重新给第一朵花找叶子,注意,这一步表现在代码上为递归实现find(1),然后找可以与第一朵花匹配的叶子。先找到第二片叶子,但是cover[2]已经标记过了,那就继续往下找,发现第一朵花还可以与第三片叶子匹配,所以就把第一朵花与第三片叶子匹配,返回true,如图:
这里写图片描述
现在第一朵花名花有主了,可以返回到第三朵花的匹配,匹配成功,返回主程序。匹配结果如图,淡蓝色粗线即为匹配成功:
这里写图片描述
表现为代码如下:

注意,在从主程序进入find时,cover要清空
function find(i:longint):boolean;
var
  q,k:longint;
begin
  find:=true;
  q:=0;
  for k:=1 to m do
    if map[i,k] and not cover[k] then
      begin
        q:=link[k]; //先记录叶子k刚开始的匹配花编号,若用花i与之匹配不成功则叶子k仍如花q匹配
        link[k]:=i; //表示叶子k与花i匹配
        cover[k]:=true;//标记叶子k的cover
        if (q=0)or(find(q)) then exit;//如果叶子k一开始就是没有匹配的,或花q可以找到别的匹配,则匹配成功,返回true
        link[k]:=q;//匹配不成功,叶子k仍与花q匹配
      end;
  find:=false;
end;

补充一下,最大匹配是 O ( n 3 ) O(n^3) O(n3)
然后难点在建图而不是匹配本身
对于建好的二分图,用有向边从固定的一边连向另一边;
对于边的储存,用邻接图和链表(前向星)均可;

当然,这只是最大匹配初步,关于最大匹配还有很多衍生问题,有待探究。

代码

var
  n,m,s,i,j,k,ans:longint;
  cover:array[1..100]of boolean;
  link:array[1..100]of longint;
  map:array[1..100,1..100]of boolean;

function find(i:longint):boolean;
var
  q,k:longint;
begin
  find:=true;q:=0;
  for k:=1 to m do
    if map[i,k] and not cover[k] then
      begin
        q:=link[k];  
        link[k]:=i; 
        cover[k]:=true;
        if (q=0)or(find(q)) then exit;
        link[k]:=q;
      end;
  find:=false;
end;

begin
  readln(n,m);
  readln(s);
  for i:=1 to s do
    begin
      readln(j,k);
      map[j,k]:=true;
    end;
  for i:=1 to n do
    begin
      fillchar(cover,sizeof(cover),false);
      find(i);
    end;
  for i:=1 to m do
    if link[i]>0 then inc(ans);
  writeln(ans);
end.

现在,我们再来一个C++的

#include <cstdio>
#include <cstring>

using namespace std;

int n,m,s,ans;
int ls[105],ne[10004],y[10004],link[105];
bool cover[105];

bool dfs(int k){
	for (int i=ls[k];i;i=ne[i])
	if (!cover[y[i]]){
		int t=link[y[i]];
		link[y[i]]=k;
		cover[y[i]]=1;
		if (t==0||dfs(t)) return true;
		link[y[i]]=t;
	}
	return false;
}

int main(){
	scanf("%d%d",&n,&m);
	scanf("%d",&s);
	for (int i=1;i<=s;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		ne[i]=ls[a];ls[a]=i;y[i]=b;
	}
	for (int i=1;i<=n;i++){
		memset(cover,0,sizeof(bool)*102);
		dfs(i);
	}
	for (int i=1;i<=n;i++)
		if (link[i]) ans++;
	printf("%d\n",ans);
}

水月将碎,镜花欲裂,逢场作戏终要行至幕落。——《君有疾否》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值