算法学习笔记:网络流#6——dinic 求解费用流

算法学习笔记:网络流#6——dinic 求解费用流

  • 1. 前言
  • 2. 详解
  • 3. 总结

1. 前言

本篇博文将会重点讲解 dinic 求解费用流。

费用流全称:最小费用最大流,其一般的问题描述如下:

给出一张网络 G = < V , E > G= G=<V,E>,每条边有两个权值: f , v f,v f,v

f f f 表示这条边的最大流量, v v v 表示单位花费,也就是说从这条边每流过一单位流量就要增加 v 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 
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 就好。

你可能感兴趣的:(图论,学习笔记,+,专项训练)