最大流


最大流:

定義:

有一張圖,要求從源點流向匯點的最大流量(可以有很多條路到達匯點)

概念:

殘量網絡:

定義 \(c_f(u,v)\) 為邊的流量之差,表示這條邊的容量和流量之差,即:

\[c_f(u,v)=c(u,v)-f(u,v) \]

對於流函數 \(f\) ,殘存網絡 \(G_f\) 是網絡 \(G\)所有節點和剩余容量大於 \(0\) 的邊 構成的子圖。

形式化的定義: \(G_f=(V_f=V,E_f=\left\{(u,v)\in E,c_f(u,v)>0\right\})\)

注意:剩余容量大於零的邊可能不在原圖 \(G\) 中。

殘量網絡中包括了那些還剩了流量空間的邊構成的圖,也包括反向邊。

增廣路:

在原圖 \(G\) 中若一條從源點到匯點的路徑上所有邊的 剩余容量大於零,這條路就被稱為增廣路。

或者說,在殘存網絡 \(G_f\) 中,一條從源點到匯點的路徑被稱為增廣路.

在這張圖中,從 \(4 \rightarrow 3\), 先走 \(f_(4,3)=20\) 這條邊。

然后判斷 \(4 \rightarrow 2 \rightarrow 3\) 這條增廣路 的總流量為 \(20\), \(4 \rightarrow 2 \rightarrow 1 \rightarrow 3\) 這條路總流量為 \(30\).

因此最大流就是 \(20+30=50\)

算法:

\(EK\) 算法:

思想:用 \(BFS\) 不斷尋找增廣路,直到網絡上不存在增廣路為止。

分析

每輪尋找增廣路的過程中,只考慮圖中所有 \(f(u,v)<c(u,v)\) 的邊,用 \(BFS\) 找到任意一條 \(s \rightarrow t\) 的路徑,同時計算出路徑上個邊的剩余容量的最小值 \(minn\) ,則網絡的流量可以增加 \(minn\)

同時,當一條邊流量 \(f(u,v)>0\) 時,我們需要建立反向邊 \(f(v,u)=-f(u,v)\) ,此時必定有 \(f(y,x)<c(y,x)\)

在該算法中,我們需要遍歷正反邊,利用 鄰接表成對存儲 的技巧,也就是
\(2^1=3,3^1=2\) 這樣能相互得到,存儲標號從 \(2\) 開始。

對於每條邊,我們只記錄剩余容量 \(c(u,v)-f(u,v)\)

當一條邊經過一條流量為 \(e\) 的邊,令 \((u,v)\) 的剩余流量減少 \(e\) ,\((v,u)\) 的剩余容量增加 \(e\)

想法就像這張圖:

時間復雜度為 \(O(nm^2)\) ,時間復雜度較高

代碼

#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=2e5+5,inf=0x3f3f3f3f;
#define ll long long 

int head[N],nxt[M],ver[M],tot=1,edge[M];
int n,m,s,t;
ll maxflow;
int vis[N],incf[N],pre[N];
void add(int x,int y,int z){
    ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; edge[tot]=z;
    ver[++tot]=x; nxt[tot]=head[y]; head[y]=tot; edge[tot]=0;
}
void update(){
    int x=t;
    while(x!=s){
        int i=pre[x];
        edge[i]-=incf[t]; edge[i^1]+=incf[t];
        x=ver[i^1]; //繼續回溯
    }
    maxflow+=incf[t];
}
bool bfs(){
    memset(vis,0,sizeof(vis)); queue<int> q; q.push(s); vis[s]=1; 
    incf[s]=inf;//各邊剩余最小容量
    while(!q.empty()){
        int x=q.front(); q.pop();
        for(int i=head[x]; i; i=nxt[i]){
            int y=ver[i],z=edge[i]; 
            if(vis[y]||!z) continue;
            incf[y]=min(incf[x],z);//限制流量和當前點能流的最大流量比較
            pre[y]=i;//記錄前驅,用來找到最長路的實際方案
            q.push(y); vis[y]=1;
            if(y==t) return 1; 
        }
    }
    return 0;
}
int main(){
    cin>>n>>m>>s>>t;
    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z); add(x,y,z);
    }
    while(bfs()) update();//證明有一條新邊可以更新
    cout<<maxflow<<endl;
    system("pause");
    return 0;
}

\(Dinic\) 算法:

\(EK\) 每輪會遍歷整個殘量網絡,但只能找出一條增廣路,屬實是拉了屬於是

我們可以利用 分層圖 的性質(即滿足 \(dep[u]+1=dep[v]\) 的邊 \((u,v)\) 構成的子圖) 。

流程

  1. 在殘量網絡上 \(BFS\) 求出節點的層次,構造分層圖。

  2. 在分層圖上 \(DFS\) 尋找增廣路,在回溯時實時更新剩余容量,另外,各個點可以流向多條出邊,同時還需要剪枝等操作。

時間復雜度為 \(O(n^2m)\) ,實際上會更優。

當前弧優化

每次增廣一條路后可以看做 榨干 了這條路,既然榨干了就沒有再增廣的可能了。

但如果每次都掃描這些枯萎的邊是很浪費時間的。

那我們就記錄一下榨取到那條邊了,然后下一次直接從這條邊開始增廣,就可以節省大量的時間。這就是 當前弧優化。

具體怎么實現呢,先把鏈式前向星的 \(head\) 數組復制一份,存進 \(cur\) 數組,然后在 \(cur\) 數組中每次記錄“榨取”到哪條邊了。

代碼

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int inf=0x3f3f3f3f,N=5005,M=200005;
int nxt[M],ver[M],tot=1,edge[M],head[N];
int n,m,s,t;
ll maxflow;
int dep[N],incf[N],pre[N],cur[N];

void add(int x,int y,int z){
    ver[++tot]=y; edge[tot]=z; nxt[tot]=head[x]; head[x]=tot;
    ver[++tot]=x; edge[tot]=0; nxt[tot]=head[y]; head[y]=tot;
}

bool bfs(){
    memset(dep,0,sizeof(dep)); queue<int> q; q.push(s); dep[s]=1;
    while(!q.empty()){
        int x=q.front(); q.pop();
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i],z=edge[i];
            if(dep[y]||!z) continue;
            q.push(y);
            dep[y]=dep[x]+1;
            if(y==t) return 1;
        }
    }
    return 0;
}

int dinic(int x,int flow){//flow表示限制流量
    if(x==t) return flow;
    int totflow=0;//從這個點總共可以增廣多少流量
    for(int i=cur[x];i;i=nxt[i]){
        cur[x]=i;//記錄上一次增廣到哪條邊了
        int y=ver[i],z=edge[i];
        if(dep[y]!=dep[x]+1||!edge[i]) continue;
        int canflow=dinic(y,min(flow,z));//表示這條增廣路上能增加多少流量
        edge[i]-=canflow; edge[i^1]+=canflow;
        totflow+=canflow;//總共增加多少流量
        flow-=canflow;
        if(flow<=0) break;//當前點已經沒有流量
    }
    return totflow;
}

int main(){
    cin>>n>>m>>s>>t;
    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z); add(x,y,z); 
    }
    while(bfs()){
        memcpy(cur,head,sizeof(head));
        maxflow+=dinic(s,inf);
    } 
    cout<<maxflow<<endl;
    system("pause");
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM