1. 程式人生 > 其它 >圖論專題-學習筆記:dinic 求解費用流

圖論專題-學習筆記:dinic 求解費用流

目錄

1. 前言

本篇博文將會重點講解 dinic 求解費用流。

費用流全稱:最小費用最大流,其一般的問題描述如下:

給出一張網路 \(G=<V,E>\),每條邊有兩個權值:\(f,v\)

\(f\) 表示這條邊的最大流量,\(v\) 表示單位花費,也就是說從這條邊每流過一單位流量就要增加 \(v\) 的花費。

現在要求這張網路的最小費用最大流,也就是在保證總流量最大的情況下總費用最小。

之前筆者寫過一篇 EK 求解費用流,效率挺高,但是根據某法律:卡 EK 合法,卡 dinic 不合法!(筆者是真的不知道這法律哪來的)

於是 dinic 求解費用流還是需要掌握的。

在往下看之前,讀者應當對以下知識有所瞭解:

  1. dinic 求解最大流。
  2. SPFA 求解最短路。

沒有學過?

  1. dinic 求解最大流:演算法學習筆記:網路流#3——dinic 求解最大流
  2. SPFA 求解最短路:P3371 【模板】單源最短路徑(弱化版) 題解

2. 詳解

模板題:P3381 【模板】最小費用最大流

首先回顧一下 dinic 求解最大流的思路:

先採用 BFS 分層,在相鄰兩層之間推流,採用 DFS 形式推流,一次可以找到多條增廣路,採用當前弧優化。

那麼 dinic 如何求費用流呢?

因為出現了最小費用這一限制條件,因此我們不能簡單的對圖分層,而是先需要跑一遍最短路徑。

跑最短路徑的時候要特別注意:反向邊的費用為正向邊費用的相反數,因此 不能直接使用 dijkstra 演算法。

然後就可以愉快的 dinic 了~

因為此時已經滿足了最短路徑這一限制條件,也就是保證費用最小,因此此時直接類比 dinic 跑 DFS 就好了。

需要注意的是這裡的 dinic 有一個特別的限制條件:走過的點不能再走,這是為了防止被環卡掉。

記錄最小費用在中間記錄就好。

程式碼:

/*
========= Plozia =========
    Author:Plozia
    Problem:P3381 【模板】最小費用最大流——dinic 寫法
    Date:2021/3/30
========= Plozia =========
*/

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

typedef long long LL;
const int MAXN = 5e3 + 10, MAXM = 5e4 + 10;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m, s, t, Head[MAXN], cnt_Edge = 1, cur[MAXN];
LL dis[MAXN], ans_Flow, ans_Spend;
bool book[MAXN], vis[MAXN];
struct node{int to; LL Flow, val; int Next;} Edge[MAXM << 1];

int read()
{
    int sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
    return (fh == 1) ? sum : -sum;
}
void add_Edge(int u, int v, int w, int c) {++cnt_Edge; Edge[cnt_Edge] = (node){v, w, c, Head[u]}; Head[u] = cnt_Edge;}
LL Min(LL fir, LL sec) {return (fir < sec) ? fir : sec;}

bool SPFA()
{
    queue <int> q; q.push(s);
    memset(book, 0, sizeof(book));
    memset(dis, 0x3f, sizeof(dis));
    book[s] = 1; dis[s] = 0;
    while (!q.empty())
    {
        int x = q.front(); q.pop(); book[x] = 0;
        for (int i = Head[x]; i; i = Edge[i].Next)
        {
            int u = Edge[i].to;
            if (Edge[i].Flow && dis[u] > dis[x] + Edge[i].val)
            {
                dis[u] = dis[x] + Edge[i].val;
                if (!book[u]) {q.push(u); book[u] = 1;}
            }
        }
    }
    return dis[t] != INF;
}

LL dfs(int now, LL Flow)
{
    if (now == t || Flow == 0) return Flow;
    vis[now] = 1; LL used = 0;
    for (int i = cur[now]; i; i = Edge[i].Next)
    {
        int u = Edge[i].to; cur[now] = i;
        if (Edge[i].Flow && !vis[u] && dis[now] + Edge[i].val == dis[u])
        {
            LL Minn = dfs(u, Min(Flow - used, Edge[i].Flow));
            if (Minn)
            {
                Edge[i].Flow -= Minn; Edge[i ^ 1].Flow += Minn; used += Minn;
                ans_Spend += Edge[i].val * Minn; if (used == Minn) {vis[now] = 0; return used;}
            }
        }
    }
    vis[now] = 0;
    if (used == 0) vis[now] = 1;
    return used;
}

void dinic()
{
    while (SPFA())
    {
        LL d; memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= n; ++i) cur[i] = Head[i];
        while ((d = dfs(s, INF)) != 0) {ans_Flow += d;}
    }
}

int main()
{
    n = read(), m = read(), s = read(), t = read();
    for (int i = 1; i <= m; ++i)
    {
        int u = read(), v = read(), w = read(), c = read();
        add_Edge(u, v, w, c); add_Edge(v, u, 0, -c);
    }
    dinic(); printf("%lld %lld\n", ans_Flow, ans_Spend);
    return 0;
}

3. 總結

dinic 求解費用流的思路就是將 BFS 分層換成 SPFA 求解最短路,然後照常 DFS 就好。