Flow

Astrayt

點一下角色會有驚喜喔

課程簡介

課程內容

  • 什麼是Flow?
  • 最大流
  • 割與MCMF其一
  • 可愛的題目
  • MCMF其二
  • 更多可愛的題目

資源包

服用指示

Flow有點難,所以我很多都簡單唬爛帶過而已
IOI 不會考喔,寫的時候看codebook,不要中毒!

雖然 TOI 或其他比賽可能會就是了

順便提一下這個

什麼是Flow

差不多長右邊那樣

總之就是在圖上流來流去

直到走到終點

正式一點的講法

在一張帶有非負權重的有向圖 \(G(V,E)\) 中
(若\((u,v) \notin E\),則權重\(cap(u,v)=0\))

而權重代表是此邊的流量限制

再來圖上有兩個特殊點:源點\(s,匯點t\)

 

現在我們假定一個函數\(f(u,v)\)為流過\(u,v\)兩點的流量,則此函數必定滿足:

  • 流量限制:​\(f(u,v) \leq cap(u,v)\)

  • 流量對稱:\(f(u,v) = -f(v,u)\)

  • 流量守恆:\(\sum_{v \in V} f(u, v) = 0 \),且 \(u \in V \backslash \{ s , t \} \)

示範

CP-Algorithm那邊偷來的

示範

CP-Algorithm那邊偷來的

一些名詞解釋

剩餘網路

這張圖還能怎麼流

特別感謝FHVirus讓我偷圖

增廣路徑

還能讓Flow變更大的簡單路徑

Maximum Flow

先來個例題

就是要試著把終點塞滿嘛

可是怎麼做?

我們先想一下

怎麼找到一個從\(s到t\)的路徑

我們先想一下

怎麼找到一個從\(s到t\)的路徑

沒錯,就是BFS!

只要剩餘網路還能走到\(t\)

我們就能找到增廣路徑了

路徑上點\(cap(u,v)\)的最小值即是這次增加的大小

所以只要不斷BFS,搜到沒有辦法再找為止

就找到最大流了

還能更好!

剛剛的時間複雜度是\(O(VE^2)\)

 

如果我們直接紀錄每個節點到\(s\)的距離

就能直接把剩餘網路上的增廣路徑通通給抓出來

最多增廣\(V次,然後抓增廣最多O(VE)\)

複雜度\(O(V^2E)\)

於是我們得到了Dinic

實作如下:

struct Dinic{
    int n, s, t;
    vector<int> level, iter;
    struct edge{
        int to, cap, rev;
        edge(int v, int capacity, int i){
            to = v; cap = capacity; rev = i;
        }
    };
    vector<vector<edge>> adj;
    void init(int _n, int _s, int _t){
        s = _s; t = _t;
        n = _n; adj.assign(n + 1, vector<edge>());
    }
    void add_edge(int from, int to, int cap){
        adj[from].pb(edge(to, cap, adj[to].size()));
        adj[to].pb(edge(from, 0, adj[from].size() - 1));
    }
    void do_bfs(){
        level.assign(n + 1, 0);
        queue<int> bfs; bfs.push(s); level[s] = 1;
        while(bfs.size()){
            int u = bfs.front(); bfs.pop();
            for(auto [v, c, r]:adj[u]){
                if(c && !level[v]){
                    bfs.push(v);
                    level[v] = level[u] + 1;
                    if(v == t) return;
                }
            }
        }
    }
    int dfs(int u, int f){
        if(u == t) return f;
        //next line is an important time saving detail!
        for(int &i = iter[u]; i < adj[u].size(); ++i){ 
            auto &[v, c, r] = adj[u][i];
            if(level[v] != level[u] + 1 || c <= 0) continue;
            int ret = dfs(v, min(f, c));
            if(ret <= 0) continue;
            c -= ret;
            adj[v][r].cap += ret;
            return ret;
        }
        return 0;
    }
    int flow(){
        do_bfs();
        int maxflow = 0, f;
        while(level[t]) {
            iter.assign(n + 1, 0);
            while(f = dfs(s, inf)) maxflow += f;
            do_bfs();
        }
        return maxflow;
    }
}dinic;

其實還有其他的喔喔喔

像是Ford-Fulkerson或其他的

但會Dinic就好了

而且講師太爛不會更好的

備課時的小插曲
我在查資料之前一直以為我寫的東西是Dinic

結果他是Edmonds-Karp

最小割

MCMF其一

🈹

總之就是把點分成兩群

一群\(s\)一群\(t\)

🈹

總之就是把點分成兩群

一群\(s\)一群\(t\)

 

那最小割怎麼做呢?

就看maxflow裡面哪邊卡住了

\(若邊E(u,v)存在且不是反向邊,而cap(u,v)最後是0\)

\(則u屬於s,v屬於t\)

🈹

\(Cut(S,T) = \sum_{u \in S} \sum_{v \in T} cap(u, v) \)

🈹

那其實只要做完maxflow就可以知道哪邊是S哪邊是T了嘛

 

那接下來就是要介紹Min-Cut Max-Flow Theroem了

畢竟我們可以很感性地發現最小🈹就是最大流

不嚴謹的證明時間

觀察1:
 最大流 \(\leq\) 最小割

\(\sum_{u \in S} \sum_{v \in T} flow(u, v) \leq \sum_{u \in S} \sum_{v \in T} cap(u, v)\)

就流出去的量最多只能和限制相等


觀察2:

現在是最大流 \(\Leftrightarrow\) 沒有增廣路徑

你如果能增廣就不是最大了嘛

 

不嚴謹的證明時間

構造一下:

在剩餘網路上可以流到的點在\(S\)
不能的在\(T\)

發現對於所有\(u \in S, v \in T,f(u,v) = cap(u,v)且f(v,u) = 0\)

 

很顯而易見的,不滿足就有增廣路徑

所以他是最大流

不嚴謹的證明時間

構造一下:

而因為發現所有的\(u \in S, v \in T,f(u,v) = cap(u,v)\)

代表他已經被塞滿了!

代表\(|f| = |Cut(S,T)|\)

又因為\(|f| \leq 任何的割\)

所以可以知道這樣的\(S,T\)是最小割且最大流\(=\)最小割!

更詳細的證明: ref1, ref2

休息一下吧

來寫可愛的題目們

TIOJ 2037 & TIOJ 2134 & CSES School Dance (三題差不多)

TIOJ 1236

TIOJ 2128 

CSES Distinct Route

CSA Candles

 

我寫過的題目有點少

其實重點不在演算法上,在怎麼把問題轉成max-flow

或是轉換成min-cut的樣子

要講解嗎?

休息完再講

MCMF其二

最小費用流

這裡東西好像有點多

我介紹其中一種做法就好
 

直接看題目:

CSES Parcel Delivery

最小費用流

要找到最小費用的路徑

是不是好像有點熟悉?

 

想想最短路徑!

既然費用可能是負的,Dijkstra顯然不可用

那只要一直做SPFA就好了

最小費用流

題目說要找 \(K\) 條最短的路徑

那我們最多做 \(K\) 次SPFA

就可以知道答案了

 

時間複雜度 \(O(NMK)\)

Code:

#include <bits/stdc++.h>
using namespace std;
#define starburst ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
typedef long long ll;
#define int ll
#define vi vector<int>
#define pb push_back
#define inf 1e18
#define skip(x) if(x)continue;

struct MCF{
    int n, s, t;
    int cost[505], p[505], ep[505];
    bool vis[505], inq[505];
    struct edge{
        int v, ca, co, rev;
        edge(int _v, int _ca, int _co, int _rev): v(_v), ca(_ca), co(_co), rev(_rev){}
    };
    vector<edge> adj[505];
    void init(int _n){
        n = _n; s = 1; t = n;
        for(int i = 1; i <= n; ++i){
            cost[i] = 1e9; inq[i] = vis[i] = p[i] = ep[i] = 0;
        }
    }
    void addedge(int u, int v, int ca, int co){
        adj[u].pb(edge(v, ca, co, (int)adj[v].size()));
        adj[v].pb(edge(u, 0, -co, (int)adj[u].size() - 1));
    }
    int flow(){
        init(n);
        cost[s] = 0;
        queue<int> bfs; bfs.push(s);
        int flow = inf;
        while(bfs.size()){
            int u = bfs.front(); bfs.pop();
            inq[u] = 0; vis[u] = 1;
            for(int i = 0; i < adj[u].size(); ++i){
                edge e = adj[u][i]; int v = e.v;
                if(e.ca != 0 && cost[u] + e.co < cost[v]){
                    p[v] = u;
                    ep[v] = i;
                    cost[v] = cost[u] + e.co;
                    if(!inq[v]){
                        inq[v] = 1;
                        bfs.push(v);
                    }
                }
            }
        }
        if(vis[t] == 0) return 0;
        for(int u = t; u != s; u = p[u]){
            flow = min(flow, adj[p[u]][ep[u]].ca);
        }
        for(int u = t; u != s; u = p[u]){
            adj[p[u]][ep[u]].ca -= flow;
            adj[u][adj[p[u]][ep[u]].rev].ca += flow;
        }
        return flow;
    }
}mcmf;

void solve(){
    int n, m, k, ans = 0; cin >> n >> m >> k;
    mcmf.init(n);
    for(int i = 1; i <= m; ++i){
        int u, v, cap, cost;
        cin >> u >> v >> cap >> cost;
        mcmf.addedge(u, v, cap, cost);
    }
    while(k > 0){
        int flow = mcmf.flow();
        if(mcmf.vis[n] == 0) {
            ans = -1;
            break;
        }
        ans += min(flow, k) * mcmf.cost[n];
        k -= flow;
    }
    cout << ans;
}

signed main(){
    starburst
    int t = 1; //cin >> t;
    while(t--) solve();
}

好像沒什麼能講的了

我弱,我只備到這

大家可以去找題目寫

更多可愛的題目

CSES Distinct Routes II

CSES Task Assignment

Atcoder Library MCF

下面這題我只寫過匈牙利的解法

2021 初選pE

滿分解可以參考這篇

但我也看不懂

Flow

By ck1090329王民人