POJ 2112 Optimal Milking 最优挤奶方案 Floyd算法+二分查找+最大流

17 篇文章 0 订阅
7 篇文章 0 订阅

题目链接:POJ 2112 Optimal Milking

Optimal Milking
Time Limit: 2000MS Memory Limit: 30000K
Total Submissions: 12446 Accepted: 4494
Case Time Limit: 1000MS

Description

FJ has moved his K (1 <= K <= 30) milking machines out into the cow pastures among the C (1 <= C <= 200) cows. A set of paths of various lengths runs among the cows and the milking machines. The milking machine locations are named by ID numbers 1..K; the cow locations are named by ID numbers K+1..K+C. 

Each milking point can "process" at most M (1 <= M <= 15) cows each day. 

Write a program to find an assignment for each cow to some milking machine so that the distance the furthest-walking cow travels is minimized (and, of course, the milking machines are not overutilized). At least one legal assignment is possible for all input data sets. Cows can traverse several paths on the way to their milking machine. 

Input

* Line 1: A single line with three space-separated integers: K, C, and M. 

* Lines 2.. ...: Each of these K+C lines of K+C space-separated integers describes the distances between pairs of various entities. The input forms a symmetric matrix. Line 2 tells the distances from milking machine 1 to each of the other entities; line 3 tells the distances from machine 2 to each of the other entities, and so on. Distances of entities directly connected by a path are positive integers no larger than 200. Entities not directly connected by a path have a distance of 0. The distance from an entity to itself (i.e., all numbers on the diagonal) is also given as 0. To keep the input lines of reasonable length, when K+C > 15, a row is broken into successive lines of 15 numbers and a potentially shorter line to finish up a row. Each new row begins on its own line. 

Output

A single line with a single integer that is the minimum possible total distance for the furthest walking cow. 

Sample Input

2 3 2
0 3 2 1 1
3 0 3 2 0
2 3 0 1 0
1 2 1 0 2
1 0 0 2 0

Sample Output

2

Source


题意:

K个挤奶器(编号1~K),每个每天最多挤M头奶牛。共有C头奶牛(编号K+1~K+C)。挤奶器和奶牛间有不同长度的路。

编写程序,寻找一个方案,安排每头牛到某个挤奶器挤奶,并使得C头奶牛需要走的所有路程的最大路程最小。

分析:

因为有多个挤奶器和多头奶牛,因而要求最短路需要Floyd。然后用最大流算法求解;搜索最大距离的最小值采用二分枚举。

关键问题是构图:

构图思路:

(1)以0为源点s,n+1为汇点t。

(2)s向每头奶牛引一条边,容量为1,代表s走向每头奶牛的最大奶牛数。

(3)每个挤奶器向t引一条边,容量为M,代表每个挤奶器最多向t走M头奶牛。

(4)对于每头奶牛通向每个挤奶器的最短路,如果小于枚举当前距离,说明能够走到,那么从奶牛到挤奶器引一条边,容量为1。

(5)构图完毕。

最大流:

最大流可以用三种方法实现:

(1)EK,普通的SAP算法,因要不断BFS,复杂度较高:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

#define maxn 1010
#define INF 0x3f3f3f3f
int s, t, n, K, C, M;
int a[maxn], pre[maxn];
int flow[maxn][maxn];
int cap[maxn][maxn], mp[maxn][maxn];

int EK()
{
    queue<int> q;
    memset(flow, 0, sizeof(flow));
    int ans = 0;
    while(1)
    {
        memset(a, 0, sizeof(a));
        a[s] = INF;
        q.push(s);
        while(!q.empty())   //bfs找增广路径
        {
            int u = q.front();
            q.pop();
            for(int v = 1; v <= n; v++)
                if(!a[v] && cap[u][v] > flow[u][v])
                {
                    pre[v] = u;
                    q.push(v);
                    a[v] = min(a[u], cap[u][v]-flow[u][v]);
                }
        }
        if(a[t] == 0) break;
        for(int u = t; u != s; u = pre[u])  //改进网络流
        {
            flow[pre[u]][u] += a[t];
            flow[u][pre[u]] -= a[t];
        }
        ans += a[t];
    }
    return ans;
}
void Floyd()
{
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++) {
            if(mp[i][k] != INF)
                for(int j = 1; j <= n; j++)
                    mp[i][j] = min(mp[i][j], mp[i][k]+mp[k][j]);
        }
}
void build(int mid)
{
    n = K+C;
    memset(cap, 0, sizeof(cap));
    for(int i = K+1; i <= n; i++)
        cap[s][i] = 1;
    for(int i = 1; i <= K; i++)
        cap[i][t] = M;
    for(int i = K+1; i <= n; i++)
        for(int j = 1; j <= K; j++)
            if(mp[i][j] <= mid)
                cap[i][j] = 1;
    n = K+C+2;
}
void Solve()
{
    s = 0, t = n+1;
    int l = 0, r = 10000;
    while(l < r)
    {
        int mid = (l+r)/2;
        build(mid);
        int ans = EK();
        if(ans >= C) r = mid;
        else l = mid+1;
    }
    printf("%d\n", r);
}
int main()
{
    //freopen("poj_2112.txt", "r", stdin);
    while(~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", &mp[i][j]);
                if(mp[i][j] == 0) mp[i][j] = INF;
            }
    Floyd();
    Solve();
    }
    return 0;
}
(2)Dinic,SAP的一种优化方案,保存层次网络,用DFS增广更加快速。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

#define maxn 250
#define INF 0x3f3f3f3f

struct Edge
{
    int from, to, cap;
};

vector<Edge> EG;
vector<int> G[maxn];
int n, s, t, d[maxn], cur[maxn], mp[maxn][maxn];
int K, C, M;
void addEdge(int from, int to, int cap)
{
    EG.push_back((Edge){from, to, cap});
    EG.push_back((Edge){to, from, 0});
    int x = EG.size();
    G[from].push_back(x-2);
    G[to].push_back(x-1);
}

bool bfs()
{
    memset(d, -1, sizeof(d));
    queue<int> q;
    q.push(s);
    d[s] = 0;
    while(!q.empty())
    {
        int x = q.front();
        q.pop();
        for(int i = 0; i < G[x].size(); i++)
        {
            Edge& e = EG[G[x][i]];
            if(d[e.to] == -1 && e.cap > 0)
            {
                d[e.to] = d[x]+1;
                q.push(e.to);
            }
        }
    }
    return (d[t]!=-1);
}

int dfs(int x, int a)
{
    if(x == t || a == 0) return a;
    int flow = 0, f;
    for(int& i = cur[x]; i < G[x].size(); i++)
    {
        Edge& e = EG[G[x][i]];
        if(d[x]+1 == d[e.to] && (f = dfs(e.to, min(a, e.cap))) > 0)
        {
            e.cap -= f;
            EG[G[x][i]^1].cap += f;
            flow += f;
            a -= f;
            if(a == 0) break;
        }
    }
    return flow;
}
int Dinic()
{
    int ans = 0;
    while(bfs())
    {
        memset(cur, 0, sizeof(cur));
        ans += dfs(s, INF);
    }
    EG.clear();
    for(int i = 0; i < n; ++i)
        G[i].clear();
    return ans;
}
void Floyd()
{
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++) {
            if(mp[i][k] != INF)
                for(int j = 1; j <= n; j++)
                    mp[i][j] = min(mp[i][j], mp[i][k]+mp[k][j]);
        }
}
void build(int mid)
{
    n = K+C;
    for(int i = K+1; i <= n; i++)
        addEdge(s, i, 1);
    for(int i = 1; i <= K; i++)
        addEdge(i, t, M);
    for(int i = K+1; i <= n; i++)
        for(int j = 1; j <= K; j++)
            if(mp[i][j] <= mid)
                addEdge(i, j, 1);
    n = K+C+2;
}
void Solve()
{
    s = 0, t = n+1;
    int l = 0, r = 10000;
    while(l < r)
    {
        int mid = (l+r)/2;
        build(mid);
        int ans = Dinic();
        if(ans >= C) r = mid;
        else l = mid+1;
    }
    printf("%d\n", r);
}
int main()
{
    //freopen("poj_2112.txt", "r", stdin);
    while(~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", &mp[i][j]);
                if(mp[i][j] == 0) mp[i][j] = INF;
            }
    Floyd();
    Solve();
    }
    return 0;
}
(3)ISAP,改进SAP,更加优化,活用层次网络,只需要一遍BFS,然后进行增广。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

#define maxn 250
#define INF 0x3f3f3f3f

struct Edge
{
    int from, to, cap, flow;
};

vector<Edge> EG;
vector<int> G[maxn];
int n, s, t, d[maxn], cur[maxn], p[maxn], num[maxn], mp[maxn][maxn];
bool vis[maxn];
int K, C, M;

void addEdge(int from, int to, int cap)
{
    EG.push_back((Edge){from, to, cap, 0});
    EG.push_back((Edge){to, from, 0, 0});
    int x = EG.size();
    G[from].push_back(x-2);
    G[to].push_back(x-1);
}

void bfs()
{
    memset(vis, false, sizeof(vis));
    queue<int> q;
    vis[t] = true;
    d[t] = 0;
    q.push(t);
    while(!q.empty()) {
        int x = q.front();
        q.pop();
        for(int i = 0; i < G[x].size(); i++) {
            Edge& e = EG[G[x][i]^1];
            if(!vis[e.from] && e.cap > e.flow) {
                vis[e.from] = true;
                d[e.from] = d[x]+1;
                q.push(e.from);
            }
        }
    }
}

int augment()
{
    int x = t, a = INF;
    while(x != s) {
        Edge& e = EG[p[x]];
        a = min(a, e.cap-e.flow);
        x = EG[p[x]].from;
    }
    x = t;
    while(x != s) {
        EG[p[x]].flow += a;
        EG[p[x]^1].flow -= a;
        x = EG[p[x]].from;
    }
    return a;
}
int ISAP()
{
    int ans =0;
    bfs();
    memset(num, 0, sizeof(num));
    for(int i = 0; i < n; i++)
        num[d[i]]++;
    int x = s;
    memset(cur, 0, sizeof(cur));
    while(d[s] < n) {
        if(x == t) {
            ans += augment();
            x = s;
        }
        bool flag = false;
        for(int i = cur[x]; i < G[x].size(); i++) {
            Edge& e = EG[G[x][i]];
            if(e.cap > e.flow && d[x] == d[e.to]+1) {
                flag = true;
                p[e.to] = G[x][i];
                cur[x] = i;
                x = e.to;
                break;
            }
        }
        if(!flag) {
            int m = n-1;
            for(int i = 0; i < G[x].size(); i++) {
                Edge& e = EG[G[x][i]];
                if(e.cap > e.flow)
                    m = min(m, d[e.to]);
            }
            if(--num[d[x]] == 0) break;
            num[d[x] = m+1]++;
            cur[x] = 0;
            if(x != s)
                x = EG[p[x]].from;
        }
    }
    EG.clear();
    for(int i = 0; i < n; ++i)
        G[i].clear();
    return ans;
}

void Floyd()
{
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++) {
            if(mp[i][k] != INF)
                for(int j = 1; j <= n; j++)
                    mp[i][j] = min(mp[i][j], mp[i][k]+mp[k][j]);
        }
}
void build(int mid)
{
    n = K+C;
    for(int i = K+1; i <= n; i++)
        addEdge(s, i, 1);
    for(int i = 1; i <= K; i++)
        addEdge(i, t, M);
    for(int i = K+1; i <= n; i++)
        for(int j = 1; j <= K; j++)
            if(mp[i][j] <= mid)
                addEdge(i, j, 1);
    n = K+C+2;
}
void Solve()
{
    s = 0, t = n+1;
    int l = 0, r = 10000;
    while(l < r)
    {
        int mid = (l+r)/2;
        build(mid);
        int ans = ISAP();
        if(ans >= C) r = mid;
        else l = mid+1;
    }
    printf("%d\n", r);
}
int main()
{
    //freopen("poj_2112.txt", "r", stdin);
    while(~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", &mp[i][j]);
                if(mp[i][j] == 0) mp[i][j] = INF;
            }
    Floyd();
    Solve();
    }
    return 0;
}

时间比较:

前两个分别为EK和ISAP算法,后面的为Dinic。不知为何我写的ISAP时间要比Dinic总是慢那么一点点,不过能顺利过题目就好了。

可以看出EK效率多差了吧:

Run IDUserProblemResultMemoryTimeLanguageCode LengthSubmit Time
13475128xxx2112Accepted9584K1875MSG++2266B2014-09-25 02:39:13
13475120xxx2112Accepted1328K157MSG++3678B2014-09-25 02:35:15
13475117xxx2112Accepted1236K125MSG++2812B2014-09-25 02:33:48
13475113xxx2112Accepted1236K141MSG++2814B2014-09-25 02:33:09
13475102xxx2112Accepted1236K94MSG++2814B2014-09-25 02:26:43

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值