POJ2112_Optimal Milking_最大流解决匹配问题

本文介绍了一个涉及机器与牛匹配的问题,通过使用邻接矩阵表示各点间的距离,并采用Floyd算法预处理最短路径,利用二分枚举结合网络流算法来寻找最优匹配方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

有 K 台机器和 C 头牛,可以抽象成 K + C 个点。给出所有点之间的直接路径的距离,用邻接矩阵表示,不存在的路径和点与自身的路径用 0 表示。每一头牛要匹配一台机器,每一台机器至多匹配 M 头牛,题目保证每一头牛都能匹配上一台机器。求最佳匹配方案,使跑路最远的牛跑的路最短。

思路

首先看到问题使最小化最大值,于是便想到了二分枚举法。于是,怎么检验答案呢。
看到机器与牛有对应关系,并且机器有一个容量,从而想到了网络流的做法。具体算法过程如下:

1.预处理

读入邻接矩阵后,用 floyd 算法求出每一点到其他所有点的最短路。注意 i == j 时的处理,并且因为 0 的存在,这里可以剪枝。(AC代码中的 floyd 函数就是加入了剪枝的)。

2.二分答案

以 0 为下界,inf 为上界进行二分枚举(可以从 floyd 中返回最长路径加以优化)。然后检验最大流是否等于牛的数量。以下假设当前枚举的答案为 x。

3.建图

设一个源点和一个汇点。
从原点出发向每一台机器连一条边,容量为 M。
从每一头牛出发向汇点连一条边,容量为1(每头牛只能匹配一台机器,为此wa了好几次)。
从每一台机器出发,向它能到达的牛连一条边,容量为1。能到达的意思是,距离小于 x 的,距离大于 x 的直接忽略,不用连边。

注意

因为 x 的值对图的影响,每一次二分枚举都要重新建图。

4.Dinic 算法求最大流

PS

这个题还可以用二分图的多重匹配做,即把每一个点分裂成 M 个点,然后进行二分图匹配。

题目链接

http://poj.org/problem?id=2112

AC代码

#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>

using namespace std;

struct edge
{
    int to, cap, rev;

    edge(int a, int b, int c)
    :to(a), cap(b), rev(c)
    {}
};

const int maxn = 250;
const int inf  = 70000;

int K, C, M;
int n;
vector<edge>G[maxn];
int Map[maxn][maxn];
int level[maxn];
int iter [maxn];

void floyd()
{
    for(int k= 1; k<= n; k++)
        for(int i= 1; i<= n; i++)
        if(Map[i][k] != inf)
        for(int j= 1; j<= n; j++)
            if(Map[k][j] != inf) Map[i][j] = min(Map[i][j], Map[i][k] + Map[k][j]);
}

void Add(int from, int to, int cap)
{
    G[from].push_back(edge(to, cap, G[to].size()));
    G[to].push_back(edge(from, 0, G[from].size() - 1));
}

void bfs(int s)
{
    memset(level, -1, sizeof level);
    level[s] = 0;
    queue<int> qu;
    qu.push(s);

    while(qu.size())
    {
        int v = qu.front();qu.pop();

        for(int i= 0; i< G[v].size(); i++)
        {
            edge e = G[v][i];
            if(e.cap > 0 && level[e.to] < 0)
            {
                level[e.to] = level[v] + 1;
                qu.push(e.to);
            }
        }
    }
}

int dfs(int v, int t, int f)
{
    if(v == t) return f;

    for(int &i= iter[v]; i< G[v].size(); i++)
    {
        edge &e = G[v][i];

        if(e.cap > 0 && level[e.to] > level[v])
        {
            int d = dfs(e.to, t, min(f, e.cap));

            if(d > 0)
            {
                e.cap -= d;
                G[e.to][e.rev].cap += d;
                return d;
            }
        }
    }

    return 0;
}

int max_flow(int s, int t)
{
    int flow = 0;

    while(1)
    {
        bfs(s);
        if(level[t] < 0) return flow;

        memset(iter, 0, sizeof iter);
        while(1)
        {
            int f = dfs(s, t, inf);
            if(f == 0) break;
            flow += f;
        }
    }
}

void init(int s, int t, int x)
{
    for(int i= s; i<= t; i++)
        G[i].clear();

    for(int i= 1; i<= K; i++)
        Add(s, i, M);
    for(int i= K+1; i<= n; i++)
        Add(i, t, 1);
    for(int i= 1; i<= K; i++)
        for(int j= K+1; j<= n; j++)
    {
        if(Map[i][j] > x) continue;
        Add(i, j, 1);
    }
}

bool Judge(int x)
{
    int s = 0, t = n + 1;
    init(s, t, x);

    return max_flow(s, t) >= C;
}

int main()
{
    scanf("%d %d %d", &K, &C, &M);
    n = K + C;

    for(int i= 1; i<= n; i++)
        for(int j= 1; j<= n; j++)
        {
            scanf("%d", &Map[i][j]);
            if(Map[i][j] == 0) Map[i][j] = inf;
        }

    floyd();

    int lb = 0, ub = inf;
    while(lb <= ub)
    {
        int mid = (lb + ub) >> 1;

        if(Judge(mid)) ub = mid - 1;
        else lb = mid + 1;
    }

    cout << ub + 1 << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值