跳转至

费用流

对于一个网络图 \(G=(V,E)\),每条边都被额外赋予了一个权值:费用 \(c(u,v)\)。一个流的总费用是 \(\sum_{(u,v)\in E}{f(u,v)c(u,v)}\)

最小费用最大流

求解最小费用最大流的方法仍然是增广路算法。这个算法要求网络图初始时没有负圈。

引理

如果每次增广时都选择代价最小的增广路,那么求出的一定是最小费用最大流。

证明

考虑数学归纳法。设 \(f_i\) 表示流量为 \(i\) 时的最小费用。首先任意 \(f_i\) 一定不包含负圈。然后找最小代价的增广路进行增广显然也不会产生负圈。而且只要没有负圈就一定是最小费用的流。

spfa

根据引理,我们可以使用 Dinic+spfa 实现最小费用最大流。

模板 P3381 【模板】最小费用最大流
#include<iostream>
using namespace std;
const int N = 5e3 + 10;
const int M = 5e4 + 10;
const int INF = (int)0x3f3f3f3f3f3f3f3f;

struct Edge {
    int v, w, c;
    int next;
} pool[2 * M];
int ne = 1, head[N];

void addEdge(int u, int v, int w, int c) {
    pool[++ne] = {v, w, c, head[u]};
    head[u] = ne;
}

int n, m, s, t;

namespace dinic {
    int dis[N], dep[N], vis[N], now[N];
    int que[M], hd, tl;
    int ans;
    bool spfa() {
        for(int i = 1; i <= n; i++) {
            vis[i] = 0;
            dis[i] = INF;
            now[i] = head[i];
        }
        hd = 1, tl = 0;
        que[++tl] = s;
        dis[s] = 0;
        while(hd <= tl) {
            int u = que[hd++];
            vis[u] = 0;
            for(int i = head[u]; i; i = pool[i].next) {
                int v = pool[i].v, w = pool[i].w, c = pool[i].c;
                if(w && dis[u] + c < dis[v]) {
                    dis[v] = dis[u] + c;
                    dep[v] = dep[u] + 1;
                    if(vis[v]) continue;
                    vis[v] = 1;
                    que[++tl] = v;
                }
            }
        }
        return dis[t] != INF;
    }
    int dfs(int u, int sum, int cost) {
        if(u == t) {
            ans += sum * cost;
            return sum;
        }
        int flow = 0;
        for(int i = now[u]; i && sum; i = pool[i].next) {
            int v = pool[i].v, w = pool[i].w, c = pool[i].c;
            now[u] = i;
            if(!w || dep[v] != dep[u] + 1 || dis[v] != dis[u] + c || vis[v]) continue;
            int k = dfs(v, min(sum, w), cost + c);
            if(!k) dis[v] = 0;
            else {
                pool[i].w -= k;
                pool[i ^ 1].w += k;
                sum -= k;
                flow += k;
            }
        }
        return flow;
    }
    void calc() {
        int flow = 0;
        while(spfa()) flow += dfs(s, INF, 0);
        cout << flow << ' ' << ans << endl;
    }
}

int main() {

    cin >> n >> m >> s >> t;
    for(int i = 1; i <= m; i++) {
        int u, v, w, c;
        cin >> u >> v >> w >> c;
        addEdge(u, v, w, c);
        addEdge(v, u, 0, -c);
    }

    dinic::calc();

    return 0;
}

原始对偶

但是如果不选择边数最少的增广路,时间复杂度是没有保证的,为 \(O(nmf)\),非常劣。由于瓶颈是 spfa 不是 dfs,所以我们希望可以将 spfa 用 Dijkstra 代替。但众所周知这个图上面是有负权边的。所以这个地方我们引入势函数 \(h(x)\),并将每条边的边权置为 \(w(u,v)+h(u)-h(v)\)。发现这样只会将求出来的 \(dis\) 偏移一个常数,所以不会影响最短路。

初始时,令 \(h(x)=d_0(x)\) 即到源点的距离。这个东西需要用一遍 spfa 求出。然后根据三角不等式,所有边权就都变成非负数了,可以直接 Dijkstra。

但是增广可能导致一些边加入,一些边消失,可能影响 \(d(x)\)。但是只需要在每轮增广结束后执行 \(h(x)\gets h(x)+d(x)\)\(d(x)\) 表示引入势能后的最短路),就能保证加入的反向边也拥有非负边权。我们来证明这一点:注意到新增反向边时当且仅当它位于最短路上,因此满足 \(d(v)=d(u)+(w+h(u)-h(v))\)。此时反向边的边权为 \(w+h'(v)-h'(u)=0\ge 0\)。并且原边的边权显然保持非负。

这样时间复杂度应该是 \(O(n^2f)\)\(O(mf\log n)\)

模板 P3381 【模板】最小费用最大流
#include<iostream>
#include<queue>
using namespace std;
const int N = 5010;
const int M = 5e4 + 10;
const int INF = 0x3f3f3f3f;

struct Edge {
    int v, w, c, next;
} pool[2 * M];
int ne = 1, head[N];

void addEdge(int u, int v, int w, int c) {
    pool[++ne] = {v, w, c, head[u]};
    head[u] = ne;
}

int n, m, s, t;

namespace Dinic {
    struct Node {
        int u, d;
        inline bool operator<(const Node &b) const {
            return d > b.d;
        }
    };
    int d[N], now[N], h[N];
    void spfa() {
        static int que[N], hd, tl;
        static bool inq[N];
        for(int i = 1; i <= n; i++) h[i] = INF;
        hd = 1, tl = 0;
        que[++tl] = s;
        inq[s] = 1; h[s] = 0;
        while(hd != (tl + 1) % N) {
            int u = que[hd]; hd = (hd + 1) % N;
            inq[u] = 0;
            for(int e = head[u]; e; e = pool[e].next) {
                int v = pool[e].v, w = pool[e].w, c = pool[e].c;
                if(!w) continue;
                if(h[u] + c < h[v]) {
                    h[v] = h[u] + c;
                    if(!inq[v]) que[(++tl) %= N] = v, inq[v] = 1;
                }
            }
        }
    }
    int dijk() {
        static bool vis[N];
        static priority_queue<Node> que;
        while(!que.empty()) que.pop();
        for(int i = 1; i <= n; i++) now[i] = head[i];
        for(int i = 1; i <= n; i++) vis[i] = 0, d[i] = INF;
        que.push({s, 0}); d[s] = 0;
        while(!que.empty()) {
            int u = que.top().u; que.pop();
            if(vis[u]) continue;
            vis[u] = 1;
            for(int e = head[u]; e; e = pool[e].next) {
                int v = pool[e].v, w = pool[e].w, c = pool[e].c;
                if(!w) continue;
                if(d[u] + (c + h[u] - h[v]) < d[v]) {
                    d[v] = d[u] + (c + h[u] - h[v]);
                    que.push({v, d[v]});
                }
            }
        }
        return d[t] != INF;
    }
    int flow, cost;
    bool vis[N];
    int dfs(int u, int sum) {
        if(u == t) return sum;
        int flow = 0;
        vis[u] = 1;
        for(int e = now[u]; e && sum; e = pool[e].next) {
            now[u] = e;
            int v = pool[e].v, w = pool[e].w, c = pool[e].c;
            if(!w || d[v] != d[u] + (c + h[u] - h[v]) || vis[v]) continue;
            int tmp = dfs(v, min(sum, w));
            if(!tmp) {
                vis[v] = 1;
            } else {
                pool[e].w -= tmp;
                pool[e ^ 1].w += tmp;
                sum -= tmp;
                flow += tmp;
                cost += c * tmp;
            }
        }
        vis[u] = 0;
        return flow;
    }
    void calc() {
        spfa();
        while(dijk()) {
            for(int i = 1; i <= n; i++) vis[i] = 0;
            flow += dfs(s, INF);
            for(int i = 1; i <= n; i++) h[i] += d[i];
        }
        cout << flow << ' ' << cost << '\n';
    }
}

int main() {

    cin >> n >> m >> s >> t;
    for(int i = 1; i <= m; i++) {
        int u, v, w, c; cin >> u >> v >> w >> c;
        addEdge(u, v, w, c);
        addEdge(v, u, 0, -c);
    }

    Dinic::calc();

    return 0;
}