这是一道很好的双入门题,无论是打匈牙利还是网络流,都非常地板子,打熟吧,少年。
开始刷网络流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;
}