洛谷 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值