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

題目鏈接: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 ID User Problem Result Memory Time Language Code Length Submit Time
13475128 xxx 2112 Accepted 9584K 1875MS G++ 2266B 2014-09-25 02:39:13
13475120 xxx 2112 Accepted 1328K 157MS G++ 3678B 2014-09-25 02:35:15
13475117 xxx 2112 Accepted 1236K 125MS G++ 2812B 2014-09-25 02:33:48
13475113 xxx 2112 Accepted 1236K 141MS G++ 2814B 2014-09-25 02:33:09
13475102 xxx 2112 Accepted 1236K 94MS G++ 2814B 2014-09-25 02:26:43

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章