luogu2756:飞行员的匹配(网络流/二分图匹配)(匈牙利板子)

6 篇文章 0 订阅
1 篇文章 0 订阅

题目传送门

这是一道很好的双入门题,无论是打匈牙利还是网络流,都非常地板子,打熟吧,少年。


开始刷网络流24题的第一题,一看居然是二分图匹配。

于是老规矩,还是做双题解吧。

果断敲个匈牙利先,奶牛配对的游戏,很早就听scy讲过思路,但是第一次自己敲板子。


介绍一下匈牙利算法的思路:
1、公牛和母牛进行配对;
2、公牛们都是很不客气的绅士: 
 不客气的意思是:后到的公牛c,总是希望自己找到的母牛b把现男友a飞掉,然后与之交往。
           绅士的意思是:如果现男友a能找到其他母牛(找的过程,也是不客气但绅士),a就把b让出来给c;如果a找不到其他的母牛,c就很绅士地离开,不打搅a与b的生活。
      
介绍本题思路:
1、1-m 是黑人,等于公牛; m+1~n 是英国人,代表母牛;
2、套用匈牙利的思路,先让公牛和母牛连边,单向(公->母);
3、对于每个公,尝试(不客气但绅士地找母);
4、输出匹配。


上匈牙利的代码:

#include<cstdio>
#include<cstring>

int n,m,ans=0,len=0;
struct nod1{int h,bf,v;}a[110];
//h是邻接表中的last,
//bf是匹配数组(母牛用),v是被访问的状态(也是母牛用) 
struct nod{int x,y,gg;}b[20005];//邻接表的边 
void ins(int x,int y)//构边 
{
	len++; b[len].x=x; b[len].y=y; 
	b[len].gg=a[x].h;a[x].h=len;	
}
bool dfs(int x)//匈牙利的核心函数 
{	//x是当前公牛 
	for(int i=a[x].h;i>0;i=b[i].gg)
	{
		int y=b[i].y;//y是当前母牛 
		if(a[y].v==0)//如果母牛未被访问 
		{
			a[y].v=1;//封路,让后面的公牛无机可乘 
			if(a[y].bf==0||dfs(a[y].bf)==1)
			{//没bf 或者 现bf能找到新欢 
				a[y].bf=x;// y的 bf 改成 x(因为y原来的bf已经找到新欢了) 
				return 1;//配对成功 
			}
		}
	}
	return 0;//配对失败 
}

int main()
{
	scanf("%d %d",&m,&n);
	for(int i=1;i<=n;i++)//初始化邻接表和匹配数组 
	{
		a[i].h=a[i].bf=0;
	}
	int x,y;
	while(1)
	{
		scanf("%d %d",&x,&y);if(x==-1&&y==-1) break;
		ins(x,y);
	}
	for(int i=1;i<=m;i++)//对于每个公牛,开始跑匈牙利 
	{
		for(int j=m+1;j<=n;j++) a[j].v=0;//跑之前,先保证每个母牛都是未被访问的 
		ans+=dfs(i);//匹配成功就+1 
	}
	printf("%d\n",ans);
	if(ans==0) printf("No Solution!\n");
	else 
	{
		for(int i=1+m;i<=n;i++)
		{
			if(a[i].bf!=0)
			{
				printf("%d %d\n",a[i].bf,i);
			}
		}
	}
	return 0;
}

===============================这是二分与网络流的分解线============================


下面是网络流的板子:

网络流的思路其实很好理解,难在构图,但这是入门题,所以构图比较易懂:

网络流思路:

1、定义一个源点st 和一个汇点 ed;

2、(构图)黑人属于左边集合,英国人属于右边集合,st与所有黑人连接,所有英国人与ed连接,按题目要求让黑人与对应的英国人连接;

3、跑网络流~~

4、(输出的小技巧)跑完网络流之后,在 左右集合之间的边中,从左到右的边里,没流量的就是用过的边,对应的x->y就是匹配的,但这题的数据好像搞反的,输出的时候,要判断 y->x有流量的就是匹配,其实思路是一样的。

上代码:

#include<cstdio>
#include<cstring>
int n,m,st,ed,len=0,l[110],inf=999999999;
struct nod{int h,d;}a[110];
struct nod2{int x,y,c,gg,f;}b[20005];


void ins(int x,int y)//全部都是做双向边 
{
	len++;b[len].x=x; b[len].y=y; b[len].f=len+1;
	b[len].c=1; b[len].gg=a[x].h; a[x].h=len;
	len++;b[len].x=y; b[len].y=x; b[len].f=len-1;
	b[len].c=0; b[len].gg=a[y].h; a[y].h=len;
}
bool bfs()//主要功能是分层 帮助推流量 
{
	for(int i=0;i<=n+1;i++) a[i].d=0;
	int tou=1,wei=2;
	l[1]=st;a[st].d=1;
	while(tou<wei)
	{
		int x=l[tou];
		for(int i=a[x].h;i>0;i=b[i].gg)
		{
			int y=b[i].y;
			if(a[y].d==0&&b[i].c>0)
			{
				a[y].d=a[x].d+1;
				l[wei++]=y;
			}
		}
		tou++;
	}
	//printf("%d\n",a[ed].d);
	if(a[ed].d>0) return 1; return 0;
}
int minn(int x,int y) { return x<y?x:y; }
int dfs(int x,int k)//来到 x 点带 k 的流量 
{
	if(x==ed) return k;//到汇点了,返回记录 
	int t=0;// x点能推下去的流量 
	for(int i=a[x].h;i>0;i=b[i].gg)
	{
		int y=b[i].y;
		if(a[y].d==a[x].d+1 &&b[i].c>0 && k>t)
		{//层符合、边上有流量、流量未分完 
			int dk=dfs(y,minn(b[i].c,k-t));//y能推下去的流量dk 
			t+=dk;//x能推的流量,给了y一些,所以t增加 
			b[i].c-=dk;//正向边推 
			b[b[i].f].c+=dk;//反向边做记录 
		}	
	}
	//printf("%d %d\n",x,t);	getchar();
	if(t==0) a[x].d=0;//如果没了增广路,游戏结束 
	return t;//不管有没有,都告诉上一层,我x花了t的流量 
	
}
int main()
{
	scanf("%d %d",&m,&n);
	st=0,ed=n+1;//定义源点st、汇点 ed 
	int x,y;
	while(1)
	{
		scanf("%d %d",&x,&y);
		if(x==-1&&y==-1) break;
		ins(x,y);//x集合->y集合 
	}
	for(int i=1;i<=m;i++) ins(st,i);//从源点->x集合 
	for(int i=m+1;i<=n;i++) ins(i,ed);//从y集合->汇点 
	int ans=0;
	
	while(bfs()>0)//能分层,说明还能推流量 
	{
		while(1)//有增广路先跑,省分层时间 
		{  
			int dx=dfs(st,inf);//增广路 
			if(dx==0) break;
			ans+=dx;
		}
	} 
	printf("%d\n",ans);
	if(ans==0) printf("No Solution!");
	else
	{//输出匹配关系 y集合中只要流量流向 x ,说明x与y匹配
	//这个是反向边的流量哦 
		for(int y=m+1;y<=n;y++)
		{
			for(int i=a[y].h;i>0;i=b[i].gg)
			{
				int x=b[i].y;
				if(b[i].c>0&&x!=ed)
				{
					printf("%d %d\n",x,y);
					break;
				}
			}
		}
	}
	
	return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值