其他分享
首页 > 其他分享> > 【Coel.学习笔记】费用流的含义与基础运用

【Coel.学习笔记】费用流的含义与基础运用

作者:互联网

基本含义

在一张流网络中,最大流是不唯一的。那么给每条边再加上一个费用值,所有最大流中费用和的极值就叫费用流。对应地,费用最小值为最小费用最大流,费用最大值为最大费用最大流

算法内容

使用 EK 算法或 Dinic 算法,把 bfs 换成 SPFA 就可以求出最小费用最大流。
需要注意,当流网络上存在费用负环,那么最小费用最大流无法求出。这时需要使用消圈法删除负圈。

代码如下(使用 EK 算法):

// Problem: P3381 【模板】最小费用最大流
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3381
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Author: Coel
// 
// Powered by CP Editor (https://cpeditor.org)

#include <cstring>
#include <iostream>
#include <queue>

using namespace std;

const int maxn = 5e5 + 10, inf = 1e8;

int n, m, S, T;
int head[maxn], nxt[maxn], to[maxn], c[maxn], w[maxn], cnt;
int d[maxn], pre[maxn], incf[maxn];
bool vis[maxn];

void add(int u, int v, int x, int y) {
    nxt[cnt] = head[u], to[cnt] = v, c[cnt] = x, w[cnt] = y, head[u] = cnt++;
    nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, w[cnt] = -y, head[v] = cnt++;
}

bool spfa() {
    queue<int> Q;
    memset(d, 0x3f, sizeof(d));
    memset(incf, 0, sizeof(incf));
    Q.push(S), d[S] = 0, incf[S] = inf;
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        vis[u] = false;
        for (int i = head[u]; ~i; i = nxt[i]) {
            int v = to[i];
            if (c[i] && d[v] > d[u] + w[i]) {
                d[v] = d[u] + w[i];
                pre[v] = i;
                incf[v] = min(c[i], incf[u]);
                if (!vis[v]) {
                    Q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return incf[T] > 0;
}

void Edmond_Karp(int& flow, int& cost) {
    flow = cost = 0;
    while (spfa()) {
        int t = incf[T];
        flow += t, cost += t * d[T];
        for (int i = T; i != S; i = to[pre[i] ^ 1]) {
            c[pre[i]] -= t;
            c[pre[i] ^ 1] += t;
        }
    }
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    memset(head, -1, sizeof(head));
    cin >> n >> m >> S >> T;
    for (int i = 1; i <= m; i++) {
        int u, v, x, y;
        cin >> u >> v >> x >> y;
        add(u, v, x, y);
    }
    int flow, cost;
    Edmond_Karp(flow, cost);
    cout << flow << ' ' << cost;
    return 0;
}

实战应用

下面给出几个比较简单的例题。

网络流 24 题:运输问题

洛谷传送门
有若干个仓库和零售商店,每个仓库拥有一定货物,每个商店需要一定货物,保证仓库存放的货物量等于商店需要的货物量,从不同仓库运到不同商店的运输费用各不相同。求出运输费用的最大值和最小值。

解析:这有点像多源多汇问题,建立一个源点与仓库相连,一个汇点与商店相连,容量等于供需量,费用等于零;接下来给每个仓库和商店连边,容量正无穷,费用等于运输花费,这个问题就变成了最小费用最大流问题。
由于要同时输出最大值和最小值,这里可以在做完最大值之后还原网络并跑一边负费用,就不需要再把 spfa 再写一遍了。

// Problem: P4015 运输问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4015
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Author: Coel
// 
// Powered by CP Editor (https://cpeditor.org)

#include <cstring>
#include <iostream>
#include <queue>

using namespace std;

const int maxn = 5e4 + 10, inf = 1e8;

int n, m, S, T;
int head[maxn], nxt[maxn], to[maxn], c[maxn], w[maxn], cnt;
int dis[maxn], pre[maxn], incf[maxn];
bool vis[maxn];

void add(int u, int v, int x, int y) {
    nxt[cnt] = head[u], to[cnt] = v, c[cnt] = x, w[cnt] = y, head[u] = cnt++;
    nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, w[cnt] = -y, head[v] = cnt++;
}

bool spfa() {
    queue<int> Q;
    memset(dis, 0x3f, sizeof(dis));
    memset(incf, 0, sizeof(incf));
    Q.push(S), dis[S] = 0, incf[S] = inf;
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        vis[u] = false;
        for (int i = head[u]; ~i; i = nxt[i]) {
            int v = to[i];
            if (c[i] && dis[v] > dis[u] + w[i]) {
                dis[v] = dis[u] + w[i];
                pre[v] = i;
                incf[v] = min(c[i], incf[u]);
                if (!vis[v]) Q.push(v), vis[v] = true;
            }
        }
    }
    return incf[T] > 0;
}

int Edmond_Karp() {
    int res = 0;
    while (spfa()) {
        int t = incf[T];
        res += t * dis[T];
        for (int i = T; i != S; i = to[pre[i] ^ 1])
            c[pre[i]] -= t, c[pre[i] ^ 1] += t;
    }
    return res;
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> m >> n;
    S = 0, T = m + n + 1;
    memset(head, -1, sizeof(head));
    for (int i = 1, x; i <= m; i++) {
        cin >> x;
        add(S, i, x, 0);
    }
    for (int i = 1, x; i <= n; i++) {
        cin >> x;
        add(m + i, T, x, 0);
    }
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++) {
            int cost;
            cin >> cost;
            add(i, m + j, inf, cost);
        }
    cout << Edmond_Karp() << '\n';
    for (int i = 0; i < cnt; i += 2) {
        c[i] += c[i ^ 1], c[i ^ 1] = 0;
        w[i] = -w[i], w[i ^ 1] = -w[i ^ 1];
    }
    cout << -Edmond_Karp();
    return 0;
}

网络流 24 题:负载平衡问题

转眼间网络流 24 题已经做完三分之一了……
洛谷传送门
有 \(n\) 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 \(n\) 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。

解析:这题实际上是一个贪心题,贪心做法略,留给读者锻炼思维。
费用流做法的话,把仓库分成两大类:存储量大于平均数,存储量小于平均数。
类似上题,把存储量大的与源点相连,存储量小的与汇点相连,容量等于存储量与平均数之差的绝对值。再给相邻的仓库连边,容量正无穷,费用等于 1。

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    S = 0, T = n + 1;
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        ave += a[i];
        add(i, i < n ? i + 1 : 1, inf, 1);
        add(i, i > 1 ? i - 1 : n, inf, 1);
    }
    ave /= n;
    for (int i = 1; i <= n; i++)
        if (a[i] > ave)
            add(S, i, a[i] - ave, 0);
        else if (a[i] < ave)
            add(i, T, ave - a[i], 0);
    cout << Edmond_Karp();
    return 0;
}

标签:费用,cnt,int,含义,Coel,笔记,maxn,incf,head
来源: https://www.cnblogs.com/Coel-Flannette/p/16477163.html