洛谷 P2756 飞行员配对方案问题
这题是二分图匹配,题目要求输出最大的配对数,以及配对方案。
学过网络流之后这题就可以写了,除了外籍飞行员和英国皇家飞行员之外,我们建立一个源点-- 0 和汇点-- n+1。
让源点连接外籍飞行员,建立一条边流量为1的边,然后建立反边。再给英国皇家飞行员连接上汇点,同样边的流量为1,反边流量为0。
这样的话网络流的图就建立好了,第一问求的最大匹配数,就是这个图的最大流。对于配对方案的话,一般的写法就是便利所有的外籍飞行员,输出正向边流量为0的就可以了。
我的写法比较特殊,因为考虑到我们的流可能存在一条边被走了两次,所以,也就是说某个外籍飞行员本身已经匹配过了,但是,另外一个外籍飞行员进行匹配时,走了原本飞行员的配对方案,所以两条路径,一条路径的长度起码包括4个点,另一条路径就是直接的配对方案数。
如果此时直接输出路径,肯定就输出了三条以上的路径。为了解决这个问题,我们知道,如果有一条边的长度大于两个点,另一条路径的长度等于两个点,并且这两条路都走过同一条边,说明,路径长的那条路,肯定是后走的,短的路肯定是先走的。
所以我们就从后向前输出,只输出匹配方案数条,并且,如果某个外籍飞行员输出国匹配方案数,我们就不在输出有关他的匹配方案,因为长路径可能交叉。
对于长路径,如果两个一对输出的话,其实就是匹配方案,画个图观察一下。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;
const int maxn=11000;
const int INF=1<<30;
int m,n,ecnt,s,t;
struct Edge {
int v,w,next;
};
struct Node {
int u,ecnt;
};
Edge edge[2*maxn];
Node pre[maxn];
bool vis[maxn];
int head[maxn];
bool map[110][110];
int road[maxn];
int rcnt=0;
void addEdge(int u,int v,int w)
{
edge[ecnt].v=v;
edge[ecnt].w=w;
edge[ecnt].next=head[u];
head[u]=ecnt++;
}
void init()
{
ecnt=0;
s=0;
t=n+1;
memset(edge,0,sizeof(edge));
memset(head,-1,sizeof(head));
for (int i=1;i<=m;i++) {
addEdge(0,i,1);
addEdge(i,0,0);
}
for (int i=m+1;i<=n;i++) {
addEdge(i,t,1);
addEdge(t,i,0);
}
}
bool bfs()
{
memset(vis,0,sizeof(vis));
memset(pre,-1,sizeof(pre));
pre[s].u=s;
vis[s]=true;
queue<int> q;
q.push(s);
while (!q.empty()) {
int u=q.front();
q.pop();
for (int i=head[u];i+1;i=edge[i].next) {
int v=edge[i].v;
if (!vis[v]&&edge[i].w) {
vis[v]=true;
pre[v].u=u;
pre[v].ecnt=i;
if (v==t) {
return true;
}
q.push(v);
}
}
}
return false;
}
int EK()
{
int ans=0;
while (bfs()) {
int mi=INF;
for (int i=t;i!=s;i=pre[i].u) {
mi=min(mi,edge[pre[i].ecnt].w);
if (i!=t) {
road[rcnt++]=i;
}
}
for (int i=t;i!=s;i=pre[i].u) {
edge[pre[i].ecnt].w-=mi;
edge[pre[i].ecnt^1].w+=mi;
}
ans+=mi;
}
return ans;
}
int main()
{
scanf("%d%d",&m,&n);
init();
int u,v;
while (scanf("%d%d",&u,&v)!=EOF) {
if (u==-1&&v==-1) {
break;
}
addEdge(u,v,1);
addEdge(v,u,0);
//map[u][v]=true;
}
int ans=EK();
printf("%d\n",ans);
memset(vis,0,sizeof(vis));
for (int i=rcnt-1;i>0;i-=2) {
if (ans==0) {
break;
}
if (!vis[road[i]]) {
ans--;
vis[road[i]]=true;
printf("%d %d\n",road[i],road[i-1]);
}
}
return 0;
}
//一个有用的样例
//5 10
//3 8
//3 7
//1 7
//-1 -1