飞行员配对方案问题

飞行员配对方案问题(二分图最大匹配)

题目描述

问题描述: 
第二次世界大战时期,英国皇家空军从沦陷国征募了大量外籍飞行员。由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的2名飞行员,其中1名是英国飞行员,另1名是外籍飞行员。在众多的飞行员中,每一名外籍飞行员都可以与其他若干名英国飞行员很好地配合。如何选择配对飞行的飞行员才能使一次派出最多的飞机。对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。 
编程任务: 
对于给定的外籍飞行员与英国飞行员的配合情况,编程找出一个最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。

输入格式 2205.in

文件第1行有2个正整数m和n。n是皇家空军的飞行员总数(n<100);m是外籍飞行员数。外籍飞行员编号为1~m;英国飞行员编号为m+1~n。
接下来每行有2个正整数i 和j,表示外籍飞行员i 可以和英国飞行员j配合。文件最后以2个-1结束。

输出格式 2205.out

第1行是最佳飞行员配对方案一次能派出的最多的飞机数M。

    这题是经典的二分图最大匹配问题。

    [ 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(iin A,j in B),则称图G为一个二分图]

    我们可以将原题的英国飞行员和外籍飞行员都抽象为图中的结点。可以这样构图: 

    外籍飞行员属于A集合,英国飞行员属于B集合。在原图中增加源点S和汇点T。

    S向A集合中每个顶点连一条容量为1的有向边。

    A、B集合之间的边都设为从A集合中的点到B集合之中的点,容量为1的有向边。

    Y集合中每个顶点向T连一条容量为1的有向边。

   (当然,反向边也是必须建的)

    即:


    然后,在原图上跑一遍最大流,最大流即为最大匹配数。X,Y之间的满流边就是一组可行解。

    为什么这样构图?为什么这样做即是答案?

    我们需要构造这样一个图,使得若干对飞行员匹配成功之后,出发的外籍(英国)飞行员尽可能多。而这恰好就是最大流的具体体现。

    我们只在可以匹配的飞行员之间连边,使得当且仅当能够匹配时,才有可能是可行的方案。每对飞行员之间边的权值为INF。

    由于每对飞行员只能飞一架飞机,飞行员只能一一匹配,因而从源点S连出的边权值为1,从B到汇点T的边权值也为1。

    这样做,每个流只能为1,它的意义为:有一驾飞机能够出发。跑出的最大流,自然就是最多的飞机数量。

    其实,对于二分图最大匹配图,构图的关键可能就是,增加源点S和汇点T,使得流为合法解,且保证匹配是一一对应的。总之,必须要清楚每个流的意义,如果它与题目所求相契合,自然是正确的。

 

代码如下:

#include
   
   
    
    
#include
    
    
     
     
#include
     
     
      
      
using namespace std;
const int MAXN=205,INF=10000000;
int head[MAXN],cur;
int v[MAXN*2],m,n,s,t,a,b;
int ans;
struct yy
{
	int to,next,va,type;
}edge[MAXN*MAXN];
void add(int from,int to,int va,int type)
{
	edge[++cur].to=to;
	edge[cur].next=head[from];
	edge[cur].va=va;
	edge[cur].type=type;
	head[from]=cur;
}
void init()
{
	memset(head,-1,sizeof(head));
	cin>>m>>n;
	s=0,t=n+1;
	while(1)
	{
		cin>>a>>b;
		if(a==-1&&b==-1)	break;
		add(a,b,INF,0);
		add(b,a,0,1);
	}
	for(int i=1;i<=m;i++)	
	{
		add(0,i,1,0);
		add(i,0,0,1);
	}
		
	for(int i=m+1;i<=n;i++)
	{
		add(i,t,1,0);
		add(t,i,0,1);
	}
		
}
int dfs(int cur,int mina)
{
	if(cur==t)	return mina;
	v[cur]=1;
	int h=head[cur];
	while(h!=-1)
	{
		int to=edge[h].to,va=edge[h].va,ty=edge[h].type,c;
		if(v[to]==0&&va!=0)
		{
			int res=dfs(to,min(mina,va));
			if(res!=0)	
			{
				edge[h].va-=res;
				if(ty==0)	c=h+1;
				else	c=h-1;
				edge[c].va+=res;
				return res;
			}
		}
		h=edge[h].next;
	}
	return 0;
}
int main()
{
	freopen("2205.in","r",stdin);
	freopen("2205.out","w",stdout);	
	ios::sync_with_stdio(false);
	init();	
	while(1)
	{
		memset(v,0,sizeof(v));
		int res=dfs(s,INF);
		if(res==0)	break;
		ans+=res;
	}
	if(ans!=0)	cout<
      
      
       
       <
       
       
      
      
     
     
    
    
   
   


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值