淺談網絡最大流

 

 

什麼是網絡最大流?html

通俗的來說,就是給你一個圖,有水流從源點S流經不少路線,問你最終能到達匯點S的水流最大是多少。c++

好比這張圖算法

從原點0到匯點3的最大流就是2,路線是0→1→3和0→2→3網絡

 

那麼對於這個網絡有幾條顯然的性質:spa

1.除了源點和匯點,流進每一個點的流量=流出每一個點的流量3d

2.每條邊的實際流量不能大於它的最大容量code

 

EK(Edmond—Karp)增廣路算法

什麼是增廣路?htm

增廣路是指從S到T的一條路,流過這條路,使得當前的流量能夠增長。blog

那你求最大流的過程不就是不斷尋找可以使流量增長的增廣路的問題嗎?it

顯然當沒有增廣路時就出來答案了。

那怎麼尋找增廣路?

EK算法就是經過bfs來找增廣路。

從s開始不斷向外廣搜,經過權值大於0的邊(由於後面會減邊權值,因此可能存在邊權爲0的邊),直到找到t爲止,而後找到該路徑上邊權最小的邊,記爲mi,而後最大流加mi,而後把該路徑上的每一條邊的邊權減去mi,直到找不到一條增廣路(從s到t的一條路徑)爲止。

上面一小段是洛穀日報的話

舉個栗子:咱們如今有這樣一張圖

 

 

 

 

假設咱們從0開始bfs後,走到1,從1開始bfs,走到3。這就出現了一條增廣路。咱們把走過的邊加紅處理

 

 

在這一條增廣路上最小的邊爲1->3=2,那麼咱們把加紅的邊的權值都減去2

 

 

 

同理,再找到一條增廣路0->2->3,這條路上最小的權值是1,那麼咱們把這條路徑上的邊的權值都減去1

 

 

而後又有0->1->2->3,相同的處理

 

 

那麼最後的答案就是4

 

看到這裏可能有人要問了。若是我在bfs 1->3這條路徑以前先bfs了1->2,那最後的答案不就是不對了嗎?

 

 

是的。正由於bfs會考慮全部的狀況,天然也會有不優的解。爲了防止這種狀況的發生,咱們設置一個神奇的東西——反向邊

爲何要建反向邊?一下子你就知道了

 

如圖,剛開始反向邊的權值都是0

 

在你進行對原有的邊減去增廣路上最小邊的操做時,把反向邊的權值加上這個值

這會產生什麼影響?

發如今對一個點bfs的時候,也會走反向邊。

對!反向邊的做用就是使程序有反悔的機會。

時間複雜度O(nm2),實際運用中可以處理103~104的網絡

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+6,M=2e5+6,inf=1e9+7;
int n,m,s,t,ans,now[N],pre[N];
bitset<N> v;

struct edge 
{
    int to,dis,nxt;
}edg[M];
int head[N],cnt=1;

inline void add(int u,int v,int w)
{
    edg[++cnt].dis=w;
    edg[cnt].to=v;
    edg[cnt].nxt=head[u];
    head[u]=cnt;
}

inline bool bfs()//尋找增廣路 
{
    v.reset();
    queue<int> q;
    q.push(s);
    v[s]=1;
    now[s]=inf;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=edg[i].nxt)
        {
            int y=edg[i].to,z=edg[i].dis;
            if(v[y]||!z) continue ;
            now[y]=min(now[x],z);
            pre[y]=i;
            if(y==t) return 1;
            q.push(y);
            v[y]=1;
        }
    }
    return 0;
}

inline void upd()//更新 
{
    ans+=now[t];
    int x=t;
    while(x!=s)
    {
        int i=pre[x];
        edg[i].dis-=now[t];
        edg[i^1].dis+=now[t];
        x=edg[i^1].to;
    }
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,0);//反向邊 
    }
    while(bfs()) upd();
    cout<<ans;
}

 

 

Dinic算法

殘餘網絡:網絡中全部節點以及剩餘容量大於0的邊組成殘餘網絡

EK算法慢的緣由是它每次有可能遍歷整個殘餘網絡可是隻找出一條增廣路

dinic算法就是每次尋找多條增廣路的算法

咱們引入一個分層圖的概念

按照節點到源點的距離(或者說是最少通過的邊數)分層

先用bfs對殘餘網絡分層

而後用dfs從當前一層向後一層反覆尋找增廣路

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+6,M=2e5+6,inf=1e9;
int n,m,s,t,ans,d[N];

struct edge
{
    int to,dis,nxt;
}edg[M];
int head[N],cnt=1;

inline void add(int u,int v,int w)
{
    edg[++cnt].dis=w;
    edg[cnt].to=v;
    edg[cnt].nxt=head[u];
    head[u]=cnt;
}

inline bool bfs()//分層 
{
    memset(d,0,sizeof(d));
    queue<int> q;
    q.push(s);
    d[s]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=edg[i].nxt)
        {
            int y=edg[i].to,z=edg[i].dis;
            if(d[y]||!z) continue;
            q.push(y);
            d[y]=d[x]+1;
            if(y==t) return 1;
        }
    }
    return 0;
}

int dfs(int u,int w)//當前的點,流入當前點的流量 
{
    if(u==t) return w;
    int sum=0;
    for(int i=head[u];i;i=edg[i].nxt)
    {
        int v=edg[i].to;
        if(edg[i].dis<=0||d[v]!=d[u]+1) continue;//流向下一層 
        int l=dfs(v,min(w-sum,edg[i].dis));//受最大流量限制 
        edg[i].dis-=l;//更新 
        edg[i^1].dis+=l;
        sum+=l;//當前已經使用的流量 
        if(sum=w) break;//一個剪枝 
    }
    if(sum>=w) d[u]=-1;
    return sum;
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,0);
    }
    int a;
    while(bfs())
    {
        while(a=dfs(s,inf)) ans+=a;
    }
    printf("%d\n",ans);
}
相關文章
相關標籤/搜索