1. 程式人生 > 實用技巧 >傳送郵件統計夜鶯系統nightingale的當前未恢復報警

傳送郵件統計夜鶯系統nightingale的當前未恢復報警

(暫時還沒有寫完)
網路流屬於圖論的一部分,一般OI上涉及到的網路流的部分只有最大流費用流

前置知識

有向圖 \(G = (V,E)\) 就叫做一個網路,每條有向邊有一個屬性 $c(x, y) $ 叫做\((x, y)\) 的容量,即這條邊最多所能容納的流量,一般來說在一個網路中還有兩個特殊的點,源點 \(s\) 和 匯點 \(t\)

網路流中有一個非常重要的函式 \(f(x, y)\),它表示流經邊 \((x, y)\) 的流量數值,因此它叫做網路的流函式

流函式的三大性質

  1. 容量限制
    即流量不能大於容量,即 \(f(x, y) <= c(x, y)\)
  2. 斜對稱
    雖然我不知道為啥叫做斜對稱,簡單的來說,我們對於一條有向邊 \((x, y)\)
    ,總是有一條它的反向邊 \((y, x)\),並且如果邊 \((x, y)\) 的流量為 \(flow\),那麼反向邊 \((y, x)\) 的流量為 \(-flow\)

原因:其實加反向邊的過程就相當於你有了反悔的機會,讓網路流的正確性得到保證,當然初學的話只需要記住別忘了加反向邊就可以。

  1. 流量守恆
    通俗的說就是源點流出的流量等於匯點接入的流量,即除了源點與匯點,別的點不產生流量,它們只是流量的搬運工。

偷 rvalue 學長的一句話:想象一個不可壓縮的流體的運輸管網, 每個管道都有防倒流閥門 (保證有向) , 每個管道還有一個單位時間內的流量限制, 那就是一個網路流模型的樣子了。

最大流

通俗的說就是源點到匯點的最大流量。
求最大流的方法有很多,這裡只講 EK 和 Dinic 兩種方法,而且一般來說最大流都是寫 Dinic,講 EK 只是為費用流做基礎。

EK 增廣路演算法

首先定義剩餘容量為邊的容量減去當前的流量的剩下的值(感性理解)。
定義一條增廣路就是從 \(s\)\(t\) 的一條路徑上每條邊的剩餘容量都大於 0 的路徑。
很顯然增廣路是可以對最大流做出貢獻的,那麼演算法的流程已經出來了,每次找增廣路,然後更新就可以了。

對於斜對稱的處理:顯然如果一條邊的剩餘容量減少 \(x\),那麼它的反向邊的剩餘容量就應該增加 \(x\),所以我們可以利用成對變換的方法,邊表的 \(cnt\)

\(2\) 開始存就可以了。

網路流不大需要分析複雜度,因為一般是非常跑不滿的,但是 EK 演算法的極限複雜度是 \(O(nm^2)\),但是在實際問題上跑個幾千個點的資料一般沒有什麼問題(邊與點同階)。

放個程式碼吧:

#include <bits/stdc++.h>
#define ll long long
const int N = 205, M = 1e4 + 5;
inline int read(int x = 0) { return scanf("%d", &x), x; } 
ll maxflow;//最大流
int n, m, s, t, head[N], cnt = 1, vis[N], pre[N], incf[N], q[N];
struct edge { int to, next, w; } e[M];
inline void add(int x, int y, int z) {
    e[++cnt] = (edge){y, head[x], z}, head[x] = cnt;
}
bool bfs() {//尋找增廣路
    memset(vis, 0, sizeof(vis));
    int l = 1, r = 0;
    q[++r] = s, vis[s] = 1, incf[s] = 0x3f3f3f3f;
    while(l <= r) {
        int u = q[l++];
        for(register int i = head[u], v; i; i = e[i].next) {
            if(vis[v=e[i].to] || e[i].w <= 0) continue;
            incf[v] = std::min(incf[u], e[i].w);
            pre[v] = i;//pre記錄這條邊的標號,便於找到具體方案
            q[++r] = v, vis[v] = 1;
            if(v == t) return 1;
        }
    }
    return 0;
}
void update() {//更新操作
    int u = t;
    while(u != s) {
        int i = pre[u];
        e[i].w -= incf[t], e[i^1].w += incf[t];
        u = e[i^1].to;
    }
    maxflow += incf[t];
}
int main() {
    n = read(), m = read(), s = read(), t = read();
    for(register int i = 1, x, y, z; i <= m; ++i) 
        x = read(), y = read(), z = read(), add(x, y, z), add(y, x, 0);//建雙向邊,且反向邊的容量設成0
    while(bfs()) update();
    printf("%lld\n", maxflow);
    return 0;
}

Dinic 演算法

Dinic演算法才是用的最多的。
我們發現 EK 演算法每次只是更新一條增廣路,嚴重增大了它的複雜度,而 Dinic 演算法是一次可以更新多條增廣路,即多路增廣