ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-07-14 17:31:55  阅读:157  来源: 互联网

标签:费用 cnt int 含义 Coel 笔记 maxn incf head


基本含义

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

算法内容

使用 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有