网络流24题 二分图基本习题
飞行员配对方案问题(二分图最大匹配)
题目描述
问题描述:
第二次世界大战时期,英国皇家空军从沦陷国征募了大量外籍飞行员。由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的2名飞行员,其中1名是英国飞行员,另1名是外籍飞行员。在众多的飞行员中,每一名外籍飞行员都可以与其他若干名英国飞行员很好地配合。如何选择配对飞行的飞行员才能使一次派出最多的飞机。对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
编程任务:
对于给定的外籍飞行员与英国飞行员的配合情况,编程找出一个最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
输入格式
文件第1行有2个正整数m和n。n是皇家空军的飞行员总数(n<100);m是外籍飞行员数。外籍飞行员编号为1~m;英国飞行员编号为m+1~n。
接下来每行有2个正整数i和j,表示外籍飞行员i可以和英国飞行员j 配合。文件最后以2个-1结束。
输出格式
第1行是最佳飞行员配对方案一次能派出的最多的飞机数M。
输入样例
5 10
1 7
1 8
2 6
2 9
2 10
3 7
3 8
4 7
4 8
5 10
-1 -1
输出样例
4
之所以说网络流很迷,是因为给一个题目,根本没办法看出是不是网络流,就比如以上那题。关于这题的解法,又要引入一个新的概念:二分图。
二分图:
二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
宏观上看,就是在构图时把题目中的点分成两条队列,并分别连接s和t。
正解:
这题运用二分图的原理,把外籍皇家军归为一列,英国皇家军归为一列。把这些点分别如上图连到s和t,边权为1。可以配合的飞行员之间也连上一条边,权值也为1。这样便利最大流,便可以得出派出的最多飞机数。
建图解析:
答案需要得到的是最大的飞机数量,正好是最大流,如同最多的水流量。因为每个飞行员只有一个,所以飞行员与起始点(s)和汇聚点(t)的边权为1。而每一个外籍飞行员同样匹配一个英国飞行员,同理。
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int maxn=205,oo=100000; int n,m,a,b,ans,v[maxn],cur=-1,head[maxn]; struct fly { int to,next,va,type; } edge[maxn*maxn]; void add(int from,int to,int va,int type) { cur++; edge[cur].to=to; edge[cur].va=va; edge[cur].type=type; edge[cur].next=head[from]; head[from]=cur; } int dfs(int now,int mi) { if(now==(n+1)) return mi; v[now]=1; int h=head[now]; while(h!=-1) { int to=edge[h].to,va=edge[h].va; if(v[to]==0&&va!=0) { int k; k=dfs(to,min(va,mi)); if(k!=0) { edge[h].va-=k; if(edge[h].type==0) edge[h+1].va+=k; else edge[h-1].va+=k; return k; } } h=edge[h].next; } return 0; }
圆桌问题
题目描述
输入格式
文件第1行有2个正整数m和n,m表示单位数,n表示餐桌数,1<=m<=150, 1<=n<=270。文件第2行有m个正整数,分别表示每个单位的代表数。文件第3行有n个正整数,分别表示每个餐桌的容量。
输出格式
如果问题有解,在文件第1行输出1,否则输出0。接下来的m行给出每个单位代表的就餐桌号。如果有多个满足要求的方案,只要输出1个方案。
输入样例
45
4 5 3 5
3 5 2 6 4
输出样例
1
1 2 4 5
1 2 3 4 5
2 4 5
1 2 3 4 5
这题的构图似乎与上题有异曲同工之妙,同样是二分图,一边为单位m,另一边是餐桌n,而与s、t连接的边权为单位的人数和餐桌人数。但是单位与餐桌之间连的边,边权要赋为oo。
后来初学者的渣渣我们,分析了一下为什么中间连的边要是oo,而飞行员那题是为1都可以(虽然老师讲解的时候说要赋为oo),以为上面那题所有的边权都是1,再怎么取 mi也只是1,但是本题就不一样啦。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn=600,oo=10000000; int n,m,cur=-1,s,t,ans,r[maxn],c[maxn],head[maxn],v[maxn],sum; struct table { int va,to,next,type; }edge[maxn*maxn]; void add(int from,int to,int va,int type) { cur++; edge[cur].to=to; edge[cur].va=va; edge[cur].type=type; edge[cur].next=head[from]; head[from]=cur; } int dfs(int now,int mi) { if(now==t) return mi; v[now]=1; int h=head[now]; while(h!=-1) { int to=edge[h].to,va=edge[h].va; if(v[to]==0&&va!=0) { int k=dfs(to,min(va,mi)); if(k!=0) { edge[h].va-=k; if(edge[h].type==0) edge[h+1].va+=k; else edge[h-1].va+=k; return k; } } h=edge[h].next; } return 0; } void step() { for(int i=1;i<=m;i++) { int h=head[i]; while(h!=-1) { int va=edge[h].va,to=edge[h].to; if(edge[h].va==0) cout<<to-m<<" "; h=edge[h].next; } cout<<endl; } } int main() { freopen("2206.in","r",stdin); freopen("2206.out","w",stdout); cin>>m>>n; memset(head,-1,sizeof(head)); for(int i=1;i<=m;i++) cin>>r[i];// for(int i=m+1;i<=m+n;i++) cin>>c[i]; s=0; t=n+m+1; for(int i=1;i<=m;i++) { sum+=r[i]; add(s,i,r[i],0); add(i,s,0,1); } for(int i=m+1;i<=m+n;i++) { add(i,t,c[i],0); add(t,i,0,1); } for(int i=1;i<=m;i++) { for(int j=m+1;j<=m+n;j++) { add(i,j,1,0); add(j,i,0,1); } } while(1) { memset(v,0,sizeof(v)); int res; res=dfs(s,oo); if(res==0) break; ans+=res; } if(ans!=sum) cout<<0<<endl; else { cout<<1<<endl; step(); } return 0; }