【網路流】網路流基本概念

HinanawiTenshi發表於2021-04-26

網路流涉及到的概念好多 \(qwq\) ,梳理一下。

流網路

流網路是一個有向圖,包含點集和邊集。即 \(G=(V,E)\)
對於邊 \(e:u\rightarrow v\) (也可以記為 \((u,v)\) ),有屬性 \(c(u,v)\) ,稱為容量。可以將其比喻為水管在單位時間可以流過的最大水量。

而圖 \(G\) 中有兩個特殊的點,稱為源點匯點,通常記為 \(s\) , \(t\) ,可以將源點比喻為無限的水源,將匯點比喻為能夠容納無窮的水的容器。

可行流

我們記 \(f(u,v)\) 為邊 \(e:u\rightarrow v\) 當前的流量,流量需要滿足兩個約束:

  1. 容量限制:即 \(0 \leq f(u,v) \leq c(u,v)\)
  2. 流量守恆:除了源點、匯點,其他點流入的流量等於流出的流量,正式地說: \(\sum_{(u,x) \in E} f(u,x) = \sum_{(x,v)\in E)} f(x,v)\)

流量值

用單位時間流出源點的流量來刻畫,記為 \(|f|=\sum_{(s,x) \in E} f(s,x) - \sum_{(y,s)\in E)}f(y,s)\)

最大流

又稱為最大可行流,即對於 \(G\) 中可行流構成的集合中,流量值最大的元素。

殘留網路

又稱為殘量網路,注意,殘留網路總是針對原圖 \(G=(V,E)\) 中的某一個可行流而言的,因此,可以殘留網路看成是可行流的一個函式,通常記為 \(G_f\)

\(G_f=(V_f,E_f)\) ,其中 \(V_f=V\)\(E_f=E和E中所有的反向邊\)

殘留網路中的容量記為 \(c'(u,v)\) ,定義為:

\[ c'(u,v)=\left\{ \begin{matrix} c(u,v)-f(u,v) && (u,v) \in E \\ f(v,u) && (v,u) \in E \end{matrix} \right. \]

增廣路徑

如果從源點 \(s\) 出發沿著殘留網路中容量大於 \(0\) 的邊走,可以走到匯點 \(t\) ,那麼將走過的邊所組成的路徑稱為增廣路徑。

原網路可行流+殘留網路可行流也是原網路的一個可行流

正式地說, \(f+f'\) 屬於 \(G\) 的一個可行流,且有 \(|f+f'|=|f|+|f'|\)

是網路中頂點的一個劃分,把所有頂點劃分成兩個頂點集合 \(S\)\(T\) ,其中源點 \(s\) 屬於 \(S\) ,匯點 \(t\) 屬於 \(T\) ,而且有 \(S \cup T=V\)\(S \cap T=\emptyset\) ,記為 \([S,T]\)

割的容量

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

最小割

\(G\) 中所有割組成的集合中,容量最小的元素。

割的流量

\(f(S,T)=\sum_{u\in S}\sum_{v\in T}f(u,v) - \sum_{u\in T}\sum_{v\in S}f(u,v)\)

任意割的流量 \(\leq\) 容量

正式地說,即 \(\forall [S,T]\)\(f(S,T)\leq c(S,T)\)

證明:(待補充)

最大流最小割定理

  1. \(f\) 是最大流
  2. \(G_f\) 不存在增廣路
  3. \(\exists [S,T]\) ,滿足 \(|f|=c(S,T)\)

最大流最小割定理指的是:上面的三個命題是等價的。(也就是說可以互推)。

證明:

  • 先證明 1 可以推得 2 :
    反證即可,如果 \(G_f\) 存在增廣路,那麼原圖 \(G\) 中存在流量大於 \(0\) 的可行流 \(|f'|\) ,那麼 \(f+f'\) 也是可行流,且流量為 \(|f|+|f'|>|f|\) ,矛盾。

  • 下證 2 可以推得 3 :
    我們將對於 \(G_f\) 中從 \(s\) 出發沿著容量大於 \(0\) 的邊可以到達的點全部放入集合 \(S\) 中,然後令 \(T=V-S\)
    那麼對於點 \(x \in S\)\(y \in T\) ,邊 \((x,y)\) 一定有 \(f(x,y)=c(x,y)\)
    而對於點 \(x \in T\)\(y \in S\) 。必然有 \(f(x,y)=0\) 。因而割可以被構造出來。

  • 最後證明 3 可以推得 1 :
    因為 \(|f|\leq 最大流 \leq c(S,T)\) ,而由 3 可知 \(|f|=c(S,T)\) ,故上式取等,即有 \(f\) 是最大流。

最大流FF方法

基於這樣的思路:

  1. 尋找增廣路
  2. 利用增廣路更新殘留網路

一直這樣做,直到找不到增廣路,那麼即可求得最大流。

演算法:

單路增廣:EK演算法

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

const int INF=1e9;
const int N=1005, M=10010;
int n, m, S, T;
struct node{
    int to, c, next;
}e[M<<1];
int h[N], tot;

// 殘量網路建圖,初始時正向的容量是 c, 反向容量是 0 。
void add(int u, int v, int cap){
    e[tot].to=v, e[tot].c=cap, e[tot].next=h[u], h[u]=tot++;
    e[tot].to=u, e[tot].c=0, e[tot].next=h[v], h[v]=tot++;  
}

int d[N], pre[N]; // d[u] 表示 S 到點 u 路徑容量的最小值, pre[u] 表示 u 的前驅邊。
bool vis[N];
int q[N];

// bfs 找增廣路。
bool bfs(){
    memset(vis, false, sizeof vis);
    int hh=0, tt=-1;
    q[++tt]=S, vis[S]=true, d[S]=INF;

    while(tt>=hh){
        int hd=q[hh++];
        for(int i=h[hd]; ~i; i=e[i].next){
            int go=e[i].to;
            if(vis[go] || !e[i].c) continue;
            vis[go]=true, q[++tt]=go;
            d[go]=min(d[hd], e[i].c);
            pre[go]=i;
            if(go==T) return true;
        }
    }
    return false;
}

int EK(){
    int res=0;
    while(bfs()){
        res+=d[T];
        for(int i=T; i!=S; i=e[pre[i]^1].to){
            e[pre[i]].c-=d[T], e[pre[i]^1].c+=d[T];
        }
    }
    return res;
}
int main(){
    memset(h, -1, sizeof h);
    cin>>n>>m>>S>>T;
    while(m--){
        int u, v, cap; cin>>u>>v>>cap;
        add(u, v, cap);
    }   
    cout<<EK()<<endl;
    return 0;
}

多路增廣:dinic演算法
程式碼:

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

#define gc() (st==ed&&(ed=(st=buf)+fread(buf,1,100000,stdin),st==ed)?EOF:*st++)
char buf[100001],*st=buf,*ed=buf;
void read(int &a){
    a=0;char c=gc();
    while(c>'9'||c<'0')c=gc();
    while(c>='0'&&c<='9')a=a*10+c-48,c=gc();
}

const int INF=0x3f3f3f3f;
const int N=10010, M=1e5+5;

struct node{
    int to, c, next;
}e[M<<1];
int h[N], tot;

void add(int u, int v, int cap){
    e[tot].to=v, e[tot].c=cap, e[tot].next=h[u], h[u]=tot++;
    e[tot].to=u, e[tot].c=0, e[tot].next=h[v], h[v]=tot++;  
}

int n, m, S, T;

int d[N], q[N], cur[N];

bool bfs(){
    memset(d, -1, sizeof d);
    int tt=-1, hh=0;
    q[++tt]=S, d[S]=0, cur[S]=h[S];

    while(tt>=hh){
        int hd=q[hh++];
        for(int i=h[hd]; ~i; i=e[i].next){
            int go=e[i].to;
            if(d[go]==-1 && e[i].c){
                d[go]=d[hd]+1;
                cur[go]=h[go];
                if(go==T) return true;
                q[++tt]=go;
            }
        }
    }
    return false;
}

int find(int u, int limit){
    if(u==T) return limit;
    int flow=0;
    for(int i=cur[u]; ~i && flow<limit; i=e[i].next){
        cur[u]=i;
        int go=e[i].to;
        if(d[go]==d[u]+1 && e[i].c){
            int t=find(go, min(e[i].c, limit-flow));
            if(!t) d[go]=-1;
            e[i].c-=t, e[i^1].c+=t, flow+=t;
        }
    }
    return flow;
}

int dinic(){
    int res=0, flow;
    while(bfs()) while(flow=find(S, INF)) res+=flow;
    return res;
}

int main(){
    memset(h, -1, sizeof h);
    read(n), read(m), read(S), read(T);
    while(m--){
        int u, v, cap; read(u), read(v), read(cap);
        add(u, v, cap);
    }

    cout<<dinic()<<endl;

    return 0;
}

相關文章