【專題總結】網絡流與二分圖(持續更新)

POJ 2112

大意

農夫約翰有 k 臺擠奶機和 c 只奶牛。任意兩個實體(擠奶機或奶牛)都在不同的地點,因此它們之間有相隔距離。每個擠奶機可以爲 m 只奶牛擠奶。問怎樣分配擠奶機使得任意兩個實體之間的最長距離最小(每個奶牛都要分配到擠奶機,題目保證有解)。

思路

顯然這是最小化最大值問題,適合用二分查找解決。於是我們可以二分最小值 x ,看看在最長距離小於等於 x 的情況下是否有滿足要求的分配方案。於是問題轉化成了分配問題,根據題目給出的數據範圍我們可以嘗試用網絡流來解決。建圖方式如下:

  • 因爲每個擠奶機都只能供 m 只牛使用,所以想到建立超級源點,從超級源點到代表擠奶機的節點連接容量爲 m 的邊。
  • 因爲每個擠奶機可以供距其距離小於 x 的牛使用,所以想到爲距其距離小於 x 的奶牛對應的節點連容量爲 1 的邊。
  • 因爲我們要判定是否有合法的分配方案,所以想到建立超級匯點,從每個奶牛對應的節點連接一條容量爲1的邊。

然後對這個圖求最大流,若最大流值等於奶牛的數量的話存在合法的分配方案,否則不存在。

另外題中的兩兩實體之間的距離用多源最短路的算法就能夠求出來了。

代碼

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

const int maxn = 300, INF = 1e8;
int n, s, t, l, r, K, C, M, mid;
int G[maxn][maxn], d[maxn][maxn];

// 最大流用的邊
struct edge {
    int from, to, cap;
    edge(int from, int to, int cap): from(from), to(to), cap(cap) {}
};

// 求最大流的Dinic算法
struct dinic {
    int level[maxn];
    int iter[maxn];
    vector <edge> edges;
    vector <int> G[maxn];
    void addedge(int u, int v, int w) {
        G[u].push_back(edges.size());
        edges.push_back(edge(u, v, w));
        G[v].push_back(edges.size());
        edges.push_back(edge(v, u, 0));
    }
    void init(int n) {
        edges.clear();
        for(int i = 1; i <= n; i++) {
            G[i].clear();
        }
    }
    bool bfs(int s, int t) {
        queue <int> q;
        q.push(s);
        memset(level, -1, sizeof(level));
        level[s] = 0;
        while(!q.empty()) {
            int u = q.front();
            q.pop();
            for(int i = 0; i < G[u].size(); i++) {
                edge& e = edges[G[u][i]];
                if(e.cap > 0 && level[e.to] < 0) {
                    level[e.to] = level[u] + 1;
                    q.push(e.to);
                }
            }
        }
        return level[t] > 0;
    }
    int dfs(int u, int t, int f) {
        if(u == t) {
            return f;
        }
        for(int& i = iter[u]; i < G[u].size(); i++) {
            edge& e = edges[G[u][i]];
            if(e.cap > 0 && level[e.to] > level[u]) {
                int d = dfs(e.to, t, min(f, e.cap));
                if(d > 0) {
                    e.cap -= d;
                    edges[G[u][i]^1].cap += d;
                    return d;
                }
            }
        }
        level[u] = -1;
        return 0;
    }
    int max_flow(int s, int t) {
        int flow = 0;
        for(; bfs(s, t);) {
            memset(iter, 0, sizeof(iter));
            for(int f; (f = dfs(s, t, INF)) > 0; flow += f);
        }
        return flow;
    }
}o;

// 求多源最短路的floyd算法
void floyd() {
    for(int k = 1; k <= n; k++) {
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                if(d[i][k] == INF || d[k][j] == INF) {
                    continue;
                }
                d[i][j] = min(d[i][j], d[i][k] + d[j][k]);
            }
        }
    }
}

// 二分查找中用於判斷最長距離x是否合法的函數
bool ok(int x) {
    o.init(n);
    for(int i = 1; i <= K; i++) {
        o.addedge(s, i, M);
    }
    for(int i = 1; i <= K; i++) {
        for(int j = K + 1; j <= K + C; j++) {
            if(d[j][i] > x) {
                continue;
            }
            o.addedge(i, j, 1);
        }
    }
    for(int i = K + 1; i <= K + C; i++) {
        o.addedge(i, t, 1);
    }
    int f = o.max_flow(s, t);
    if(f < C) {
        return false;
    }
    return true;
}

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", &G[i][j]);
            d[i][j] = G[i][j] ? G[i][j] : (i == j ? 0 : INF);
        }
    }
    floyd();
    s = 0;
    t = K + C + 1;
    l = -1;
    r = INF;
    // 二分查找
    while(r - l > 1) {
        mid = (l + r) >> 1;
        if(ok(mid)) {
            r = mid;
        }
        else {
            l = mid;
        }
    }
    printf("%d\n", r);
    return 0;
}

HDU 3315

大意

在一次遊戲中,我方和對方各有 n 枚棋子。對方棋子出戰順序是固定的,而我方棋子的出戰順序是任意的(有一個初始順序但可以改動)。每個棋子有攻擊力和生命值兩個屬性,與對方棋子戰鬥的時候,每回合可以讓對方棋子的生命值減少其攻擊力的值。當我方棋子第 i 次出戰時戰勝對方棋子,我方可贏得 vi 分數,否則則輸掉 vi 分數。問怎樣安排我方棋子的出戰順序,使得我方得到的分數最大,還要求出順序改動的百分比,當分數同樣時優先選擇順序改動最小的方案。

思路

這是一個匹配問題,由於在匹配的情況下還要求某個權值最大,因此可以選擇的模型有最小費用最大流和最大權匹配。這裏用最小費用最大流解決。建圖方式如下:

  • 因爲雙方棋子可以任意配對,所以從我方棋子到對方所有棋子連邊,以保證雙方每兩個棋子之間都有邊相連。
  • 因爲對戰結果是可以預測的,所以每條邊的費用是此邊相連的兩個棋子能給我方帶來的分數的相反數(因爲只能求最小費用)。
  • 因爲還要知道順序改動的情況,所以先將每條邊的費用乘 1000 ,如果這條邊連接的對手棋子的編號與我方棋子的初始編號相同的話將邊權減 1 (本應該加 1 ,但是因爲邊權要取相反數所以變爲減 1 )以示獎勵。乘 1000 的原因就是爲了不讓獎勵分數影響到主要分數。
  • 建立超級源點和超級匯點,超級源點到所有我方棋子之間都要連邊,對方棋子到所有超級匯點之間也都要連邊,這些邊的費用爲 0
  • 所有邊的流量都爲 1

然後對圖求最小費用最大流。其中的最小費用爲 ans 。那麼我方能夠獲得的最大分數就是 ans/1000 。改變的順序就是 ans\mathchoicemod1000

代碼

#include <bits/stdc++.h>
using namespace std;

const int maxn = 100, maxv = 3 * maxn, maxVal = 1000, INF = 1e7;
int c, n, s, t, N, ans, score, similar;
int v[maxn], h[maxn], p[maxn], a[maxn], b[maxn];

// 圖中的邊
struct edge {
    int from, to, cap, flow, cost;
    edge(int from, int to, int cap, int flow, int cost):
        from(from), to(to), cap(cap), flow(flow), cost(cost) {}
};

// 最小費用最大流算法
struct MCMF {
    int v;
    vector <edge> edges;
    vector <int> G[maxv];
    int inq[maxv];                  // 是否在隊列中
    int d[maxv];                    // Bellman-Ford
    int p[maxv];                    // 上一條弧
    int a[maxv];                    // 可改進量
    void init(int v) {
        this->v = v;
        for(int i = 0; i < v; i++) {
            G[i].clear();
        }
        edges.clear();
    }
    void addEdge(int from, int to, int cap, int cost) {
        G[from].push_back(edges.size());
        edges.push_back(edge(from, to, cap, 0, cost));
        G[to].push_back(edges.size());
        edges.push_back(edge(to, from, 0, 0, -cost));
    }
    bool BellmanFord(int s, int t, int& flow, int& cost) {
        for(int i = 0; i < v; i++) {
            d[i] = INF;
        }
        memset(inq, 0, sizeof(inq));
        d[s] = 0;
        inq[s] = 1;
        p[s] = 0;
        a[s] = INF;
        queue <int> q;
        q.push(s);
        while(!q.empty()) {
            int u = q.front();
            q.pop();
            inq[u] = 0;
            for(int i = 0; i < G[u].size(); i++) {
                edge& e = edges[G[u][i]];
                if(e.cap > e.flow && d[e.to] > d[u] + e.cost) {
                    d[e.to] = d[u] + e.cost;
                    p[e.to] = G[u][i];
                    a[e.to] = min(a[u], e.cap - e.flow);
                    if(!inq[e.to]) {
                        q.push(e.to);
                        inq[e.to] = 1;
                    }
                }
            }
        }
        if(d[t] == INF) return false; // s-t不連通,失敗退出
        flow += a[t];
        cost += d[t] * a[t];
        for(int u = t; u != s; u = edges[p[u]].from) {
            edges[p[u]].flow += a[t];
            edges[p[u]^1].flow -= a[t];
        }
        return true;
    }
    int Mincost(int s, int t) {
        int flow = 0, cost = 0;
        while(BellmanFord(s, t, flow, cost));
        return cost;
    }
}o;

// 用於判斷勝負的函數
inline int beat(int x, int y) {
    int tx = h[x] / b[y] + (h[x] % b[y] > 0);
    int ty = p[y] / a[x] + (p[y] % a[x] > 0);
    return tx >= ty;
}

int main() {
    while(scanf("%d", &n), n) {
        // 輸入部分
        for(int i = 1; i <= n; i++) {
            scanf("%d", &v[i]);
        }
        for(int i = 1; i <= n; i++) {
            scanf("%d", &h[i]);
        }
        for(int i = 1; i <= n; i++) {
            scanf("%d", &p[i]);
        }
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        for(int i = 1; i <= n; i++) {
            scanf("%d", &b[i]);
        }
        // 建圖部分
        s = 0;
        t = 2 * n + 1;
        N = t + 1;
        o.init(N);
        for(int i = 1; i <= n; i++) {
            o.addEdge(s, i, 1, 0);
        }
        for(int i = 1; i <= n; i++) {
            o.addEdge(n + i, t, 1, 0);
        }
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                c = (beat(i, j) ? -1 : 1) * maxVal * v[i] - (i == j);
                o.addEdge(i, n + j, 1, c);
            }
        }
        // 計算結果部分
        ans = o.Mincost(s, t);
        score = -ans / maxVal;
        similar = -ans % maxVal;
        if(score > 0) {
            printf("%d %.3f%%\n", score, 100.0 * similar / n);
        }
        else {
            puts("Oh, I lose my dear seaco!");
        }
    }
    return 0;
}

HDU 3395

大意

n 個魚,它們會攻擊認爲是異性的魚,然後被攻擊的魚就會產卵。每隻魚有一個權值,魚卵的權值爲該魚卵的雙親的權值得異或和。問所有魚卵的權值之和是多少?

思路

該問題也是一個類似於分配的問題。由於題目中涉及“最大權值之和”,因此適合建模爲二分圖帶權匹配或最小費用最大流(將原權值變爲相反數)。這裏採用最小費用最大流的方法,建圖方法如下:

  • 根據題給矩陣,只要一隻魚認爲另一隻魚是異性,那麼就給這兩條魚連一條容量爲 1 ,權值爲兩隻魚權值異或和的相反數的邊。注意,這裏將一隻魚拆成兩個點,分別代表攻擊者和被攻擊者。
  • 因爲每條魚只能攻擊一次,所以建立超級源點,從超級源點向所有代表攻擊者的點連容量爲 1 ,邊權爲 0 的邊。
  • 因爲每條魚只能被攻擊一次,所以建立超級匯點,從每個被攻擊者對應的點向超級匯點連容量爲 1 ,邊權爲 0 的邊。
  • 因爲最小費用最大流算法先滿足最大流然後才考慮最小費用,因此一定要讓圖滿流(還要不影響權值)。方法是讓所有攻擊者向超級匯點連容量爲 1 ,權值爲 0 的邊。(即使有魚沒有攻擊機會,仍然能夠滿流)

然後對圖求最小費用最大流。假設最小費用是 ans ,那麼答案就是 ans

代碼

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

const int maxn = 1010, INF = 1e6;

// 圖中的邊
struct edge {
    int from, to, cap, flow, cost;
    edge(int from, int to, int cap, int flow, int cost):
        from(from), to(to), cap(cap), flow(flow), cost(cost) {}
};

// 最小費用最大流算法
struct MCMF {
    int v;
    vector <edge> edges;
    vector <int> G[maxn];
    int inq[maxn];             // 是否在隊列中
    int d[maxn];               // Bellman-Ford
    int p[maxn];               // 上一條弧
    int a[maxn];               // 可改進量
    void init(int v) {
        this->v = v;
        for(int i = 0; i < v; i++) {
            G[i].clear();
        }
        edges.clear();
    }
    void addEdge(int from, int to, int cap, int cost) {
        G[from].push_back(edges.size());
        edges.push_back(edge(from, to, cap, 0, cost));
        G[to].push_back(edges.size());
        edges.push_back(edge(to, from, 0, 0, -cost));
    }
    bool BellmanFord(int s, int t, int& flow, int& cost) {
        for(int i = 0; i < v; i++) {
            d[i] = INF;
        }
        memset(inq, 0, sizeof(inq));
        d[s] = 0;
        inq[s] = 1;
        p[s] = 0;
        a[s] = INF;
        queue <int> q;
        q.push(s);
        while(!q.empty()) {
            int u = q.front();
            q.pop();
            inq[u] = 0;
            for(int i = 0; i < G[u].size(); i++) {
                edge& e = edges[G[u][i]];
                if(e.cap > e.flow && d[e.to] > d[u] + e.cost) {
                    d[e.to] = d[u] + e.cost;
                    p[e.to] = G[u][i];
                    a[e.to] = min(a[u], e.cap - e.flow);
                    if(!inq[e.to]) {
                        q.push(e.to);
                        inq[e.to] = 1;
                    }
                }
            }
        }
        if(d[t] == INF) return false; // s-t不連通,失敗退出
        flow += a[t];
        cost += d[t] * a[t];
        for(int u = t; u != s; u = edges[p[u]].from) {
            edges[p[u]].flow += a[t];
            edges[p[u]^1].flow -= a[t];
        }
        return true;
    }
    int Mincost(int s, int t) {
        int flow = 0, cost = 0;
        while(BellmanFord(s, t, flow, cost));
        return cost;
    }
}o;

char G[maxn][maxn];
int n, s, t, ans, v[maxn];

int main() {
    while(scanf("%d", &n), n) {
        for(int i = 1; i <= n; i++) {
            scanf("%d", &v[i]);
        }
        for(int i = 1; i <= n; i++) {
            scanf("%s", G[i] + 1);
        }
        // 建圖部分
        s = 0;
        t = 2 * n + 1;
        o.init(2 * n + 2);
        for(int i = 1; i <= n; i++) {
            o.addEdge(s, i, 1, 0);
        }
        // 爲了滿流連的邊
    for(int i = 1; i <= n; i++) {
        o.addEdge(i, t, 1, 0);
    }
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                if(G[i][j] == '0') {
                    continue;
                }
                o.addEdge(i, n + j, 1, - (v[i] ^ v[j]));
            }
        }
        for(int i = 1; i <= n; i++) {
            o.addEdge(n + i, t, 1, 0);
        }
        ans = o.Mincost(s, t);
        printf("%d\n", -ans);
    }
    return 0;
}

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