飞行员配对方案问题
网络流24题-飞行员配对方案问题
题目背景
第二次世界大战期间,英国皇家空军从沦陷国征募了大量外籍飞行员。由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的两名飞行员,其中一名是英国飞行员,另一名是外籍飞行员。在众多的飞行员中,每一名外籍飞行员都可以与其他若干名英国飞行员很好地配合。
题目描述
一共有 \(n\) 个飞行员,其中有 \(m\)个外籍飞行员和\((n - m)\)个英国飞行员,外籍飞行员从 \(1\)到 \(m\) 编号,英国飞行员从 \(m + 1\)到 \(n\) 编号。 对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
输入格式
输入的第一行是用空格隔开的两个正整数,分别代表外籍飞行员的个数 \(m\) 和飞行员总数 \(n\)。
从第二行起到倒数第二行,每行有两个整数 \(u, v\),代表外籍飞行员 \(u\) 可以和英国飞行员 \(v\) 配合。
输入的最后一行保证为\(-1 -1\),代表输入结束。
输出格式
本题存在 Special Judge。
请输出能派出最多的飞机数量,并给出一种可行的方案。
输出的第一行是一个整数,代表一次能派出的最多飞机数量,设这个整数是 \(k\)。
第 \(2\) 行到第 \(k + 1\) 行,每行输出两个整数 \(u, v\),代表在你给出的方案中,外籍飞行员 \(u\) 和英国飞行员 \(v\) 配合。这 \(k\) 行的 \(u\) 与 \(v\)应该互不相同。
输入输出样例:
输入
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
1 7
2 9
3 8
5 10
思路:
较为容易的建模。
- 每一架飞机容纳两个不同国籍的人。
- 设超级源点\(S\),超级汇点\(T\)。
- 建立\(S\)到每个外籍飞行员的边,边容量为1(最多流入的流为1)。
- 建立英国飞行员到\(T\)的边,边的容量为1(最多流入的流为1)。
- 根据输入建立外籍飞行员到英国飞行员的若干条边,边的容量为1。
- 建图完毕,跑最大流可以得到最多配对数。
如何记录哪些人配对在一块呢
- 输入若干条边的时候,记录下外籍飞行员\(i\)\(\to\)英国飞行员\(j\)的每条边的编号。
- 若\(i\),\(j\)配对,则\(i\to j\)的这条有向边的剩余容量\(C(i,j)=0\)。
- 前面记录边了,暴力查剩余容量为\(0\)的边,直接输出答案即可。
Code:
/*
* @Author: Quun
* @Date: 2020-07-23 10:08:46
* @Last Modified by: Quun
* @Last Modified time: 2020-07-23 10:35:23
*/
#pragma GCC optimize("Ofast")
#include<stdio.h>
#include<iostream>
#include<queue>
#include<string.h>
#include<vector>
using namespace std;
/**
*
* Dinic 模板 求最大流+记录流路径
* 建图 技巧
*/
const int N=200000;
int s,t,m,n;
int maxflow=0;
struct node{
int to;
int next;
int dis;
}edge[N*2];
std::vector<int> v[2000],ans[2000];
int head[N*2],num_edge,u,_d,base;
void edge_add(int from,int to,int dis){
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
edge[num_edge].dis=dis;
head[from]=num_edge;
}
int vis[N];
int d[N];
bool bfs(){
memset(d,0,sizeof(d));
queue<int>q;
q.push(s);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
int z=edge[i].dis;
if(z&&d[y]==0){
q.push(y);
d[y]=d[x]+1;
if(y==t)return true;
}
}
}
return false;
}
int dinic(int x,int flow){
if(x==t||flow==0)return flow;//flow==0
int rest=flow,k;
for(int i=head[x];i;i=edge[i].next){
int z=edge[i].dis;
int y=edge[i].to;
if(z&&d[y]==d[x]+1){
k=dinic(y,min(rest,z));
if(!k)d[y]=0;//剪枝 减去增广完毕的点
edge[i].dis-=k;
edge[i^1].dis+=k;
rest-=k;
}
}
return flow-rest;
}
int main(){
int _;
int cas=1;
_=1;
while(_--){
scanf("%d %d",&m,&n);
//memset(head,0,sizeof(head));
num_edge=1;
n=n-m;
int c;
s=0;t=n+m+1;
int sum=0;
for(int i=1;i<=m;++i){//源点-外籍飞行员
edge_add(s,i,1);
edge_add(i,s,0);
}
for(int i=1;i<=n;++i){//英国飞行员-汇点
edge_add(m+i,t,1);
edge_add(t,m+i,0);
}
int l,r;
while(scanf("%d %d",&l,&r)){//外籍飞行员-英国飞行员
if(l==-1&&r==-1)break;
edge_add(l,r,1);
v[l].push_back(num_edge);//记录边的编号
edge_add(r,l,0);
}
int flow=0;
int INF=0x3f3f3f3f;
while(bfs()){
while(flow=dinic(s,INF))maxflow+=flow;
}
printf("%d\n",maxflow);
for(int i=1;i<=m;++i){
for(auto it : v[i]){//查找剩余容量为0的边
if(edge[it].dis==0)
printf("%d %d\n",i,edge[it].to);
}
}
}
return 0;
}
/*
5 10
1 7
1 8
2 6
2 9
2 10
3 7
3 8
4 7
4 8
5 10
-1 -1
*/