Dinic算法(研究總結,網絡流)

Dinic算法(研究總結,網絡流)

網絡流是信息學競賽中的常見類型,筆者剛學習了最大流Dinic算法,簡單記錄一下算法

網絡流基本概念

什麼是網絡流

在一個有向圖上選擇一個源點,一個匯點,每一條邊上都有一個流量上限(如下稱爲容量),即通過這條邊的流量不能超過這個上界,同時,除源點和匯點外,全部點的入流和出流都相等,而源點只有流出的流,匯點只有匯入的流。這樣的圖叫作網絡流數組

所謂網絡或容量網絡指的是一個連通的賦權有向圖 D= (V、E、C) , 其中V 是該圖的頂點集,E是有向邊(即弧)集,C是弧上的容量。此外頂點集中包括一個起點和一個終點。網絡上的流就是由起點流向終點的可行流,這是定義在網絡上的非負函數,它一方面受到容量的限制,另外一方面除去起點和終點之外,在全部中途點要求保持流入量和流出量是平衡的。(引自百度百科)網絡

定義

咱們定義:
源點:只有流出去的點
匯點:只有流進來的點
流量:一條邊上流過的流量
容量:一條邊上可供流過的最大流量
殘量:一條邊上的容量-流量函數

幾個基本性質

基本性質一:

對於任何一條流,總有流量<=容量學習

這是很顯然的優化

基本性質二

對於任何一個不是源點或匯點的點u,總有\[\sum_{p\in E}k[p][u]==\sum_{q\in E}k[u][q] \text{(其中k[i][j]表示i到j的流量)}\]spa

這個也很顯然,即一個點(除源點和匯點)的入流和出流相等code

基本性質三

對於任何一條有向邊(u,v),總有\[k[u][v]==-k[v][u]\]blog

這個看起來並非很好理解,它的意思就是一條邊的反邊上的流是這條邊的流的相反數,能夠這麼想,就是若是有k[u][v]的流從u流向v,也就至關於有-k[v][u]的流從v流向u。這條性質很是重要。隊列

網絡流最大流

網絡流的最大流算法就是指的一個流量的方案使得網絡中流量最大。

網絡流最大流的求解

網絡流的全部算法都是基於一種增廣路的思想,下面首先簡要的說一下增廣路思想,其基本步驟以下:

1.找到一條從源點到匯點的路徑,使得路徑上任意一條邊的殘量>0(注意是小於而不是小於等於,這意味着這條邊還能夠分配流量),這條路徑便稱爲增廣路
2.找到這條路徑上最小的F[u][v](咱們設F[u][v]表示u->v這條邊上的殘量即剩餘流量),下面記爲flow
3.將這條路徑上的每一條有向邊u->v的殘量減去flow,同時對於起反向邊v->u的殘量加上flow(爲何呢?咱們下面再講)
4.重複上述過程,直到找不出增廣路,此時咱們就找到了最大流

這個算法是基於增廣路定理(Augmenting Path Theorem): 網絡達到最大流當且僅當殘留網絡中沒有增廣路(因爲筆者知識水平不高,暫且不會證實)

舉個例子:
此處輸入圖片的描述

爲何要連反向邊

咱們知道,當咱們在尋找增廣路的時候,在前面找出的不必定是最優解,若是咱們在減去殘量網絡中正向邊的同時將相對應的反向邊加上對應的值,咱們就至關於能夠反悔從這條邊流過。
好比說咱們如今選擇從u流向v一些流量,可是咱們後面發現,若是有另外的流量從p流向v,而原來u流過來的流量能夠從u->q流走,這樣就能夠增長總流量,其效果就至關於p->v->u->q,用圖表示就是:
此處輸入圖片的描述
圖中的藍色邊就是咱們首次增廣時選擇的流量方案,而實際上若是是橘色邊的話狀況會更優,那麼咱們能夠在v->u之間連一條邊容量爲u->v減去的容量,那咱們在增廣p->v->u->q的時候就至關於走了v->u這條"邊",而u->v的流量就與v->u的流量相抵消,就成了中間那幅圖的樣子了。
若是是v->u時的流量不能徹底抵消u->v的,那就說明u還能夠流一部分流量到v,再從v流出,這樣也是容許的。

一個小技巧

雖說咱們已經想明白了爲何要加反向邊,但反向邊如何具體實現呢?筆者在學習網絡流的時候在這裏困擾了很久,如今簡要的總結在這裏。
首先講一下鄰接矩陣的作法,對於G[u][v],若是咱們要對其反向邊進行處理,直接修改G[v][u]便可。
但有時會出現u->v和v->u同時原本就有邊的狀況,一種方法是加入一個新點p,使u->v,而v->u變成v->p,p->u。
另外一種方法就是使用鄰接表,咱們把邊從0開始編號,每加入一條原圖中的邊u->v時,加入邊v->u流量設爲0,那麼這時對於編號爲i的邊u->v,咱們就能夠知道i^1就是其反向邊v->u。

樸素算法的低效之處

雖說咱們已經知道了增廣路的實現,可是單純地這樣選擇可能會陷入很差的境地,好比說這個經典的例子:
此處輸入圖片的描述
咱們一眼能夠看出最大流是999(s->v->t)+999(s->u->t),但若是程序採起了不恰當的增廣策略:s->v->u->t
此處輸入圖片的描述
咱們發現中間會加一條u->v的邊
此處輸入圖片的描述
而下一次增廣時:
此處輸入圖片的描述
若選擇了s->u->v->t
此處輸入圖片的描述
而後就變成
此處輸入圖片的描述
這是個很是低效的過程,而且當圖中的999變成更大的數時,這個劣勢還會更加明顯。
怎麼辦呢?
這時咱們引入Dinic算法

Dinic算法

爲了解決咱們上面遇到的低效方法,Dinic算法引入了一個叫作分層圖的概念。具體就是對於每個點,咱們根據從源點開始的bfs序列,爲每個點分配一個深度,而後咱們進行若干遍dfs尋找增廣路,每一次由u推出v必須保證v的深度必須是u的深度+1。下面給出代碼
一些變量的定義

int s,t;//源點和匯點
int cnt;//邊的數量,從0開始編號。
int Head[maxN];//每個點最後一條邊的編號
int Next[maxM];//指向對應點的前一條邊
int V[maxM];//每一條邊指向的點
int W[maxM];//每一條邊的殘量
int Depth[maxN];//分層圖中標記深度

Dinic主過程:

int Dinic()
{
    int Ans=0;//記錄最大流量
    while (bfs())
    {
        while (int d=dfs(s,inf))
            Ans+=d;
    }
    return Ans;
}

bfs分層圖過程

bool bfs()
{
    queue<int> Q;//定義一個bfs尋找分層圖時的隊列
    while (!Q.empty())
        Q.pop();
    memset(Depth,0,sizeof(Depth));
    Depth[s]=1;//源點深度爲1
    Q.push(s);
    do
    {
        int u=Q.front();
        Q.pop();
        for (int i=Head[u];i!=-1;i=Next[i])
            if ((W[i]>0)&&(Depth[V[i]]==0))//若該殘量不爲0,且V[i]還未分配深度,則給其分配深度並放入隊列
            {
                Depth[V[i]]=Depth[u]+1;
                Q.push(V[i]);
            }
    }
    while (!Q.empty());
    if (Depth[t]==0)//當匯點的深度不存在時,說明不存在分層圖,同時也說明不存在增廣路
        return 0;
    return 1;
}

dfs尋找增廣路過程

int dfs(int u,int dist)//u是當前節點,dist是當前流量
{
    if (u==t)//當已經到達匯點,直接返回
        return dist;
    for (int i=Head[u];i!=-1;i=Next[i])
    {
        if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))//注意這裏要知足分層圖和殘量不爲0兩個條件
        {
            int di=dfs(V[i],min(dist,W[i]));//向下增廣
            if (di>0)//若增廣成功
            {
                W[i]-=di;//正向邊減
                W[i^1]+=di;反向邊加
                return di;//向上傳遞
            }
        }
    }
    return 0;//不然說明沒有增廣路,返回0
}

把上面的內容都封裝到類中:

class Graph
{
private:
    int s,t;
    int cnt;
    int Head[maxN];
    int Next[maxM];
    int V[maxM];
    int W[maxM];
    int Depth[maxN];
public:
    int n;
    void init(int nn,int ss,int tt)//初始化
        {
            n=nn;
            s=ss;
            t=tt;
            cnt=-1;
            memset(Head,-1,sizeof(Head));
            memset(Next,-1,sizeof(Next));
            return;
        }
    void _Add(int u,int v,int w)
        {
            cnt++;
            Next[cnt]=Head[u];
            V[cnt]=v;
            W[cnt]=w;
            Head[u]=cnt;
        }
    void Add_Edge(int u,int v,int w)//加邊,同時加正向和反向的
        {
            _Add(u,v,w);
            _Add(v,u,0);
        }
    int dfs(int u,int dist)
        {
            //cout<<"Dfs:"<<u<<' '<<dist<<endl;
            if (u==t)
                return dist;
            for (int i=Head[u];i!=-1;i=Next[i])
            {
                if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))
                {
                    int di=dfs(V[i],min(dist,W[i]));
                    if (di>0)
                    {
                        W[i]-=di;
                        W[i^1]+=di;
                        return di;
                    }
                }
            }
            return 0;
        }
    int bfs()
        {
            //cout<<"Bfs.begin:"<<endl;
            queue<int> Q;
            while (!Q.empty())
                Q.pop();
            memset(Depth,0,sizeof(Depth));
            Depth[s]=1;
            Q.push(s);
            do
            {
                int u=Q.front();
                //cout<<u<<endl;
                Q.pop();
                for (int i=Head[u];i!=-1;i=Next[i])
                {
                    if ((W[i]>0)&&(Depth[V[i]]==0))
                    {
                        Depth[V[i]]=Depth[u]+1;
                        Q.push(V[i]);
                    }
                }
            }
            while (!Q.empty());
            //cout<<"Bfs.end"<<endl;
            if (Depth[t]>0)
                return 1;
            return 0;
        }
    int Dinic()
        {
            int Ans=0;
            while (bfs())
            {
                while (int d=dfs(s,inf))
                    Ans+=d;
            }
            return Ans;
        }
};

Dinic算法的優化

Dinic算法還有優化,這個優化被稱爲當前弧優化,即每一次dfs增廣時不從第一條邊開始,而是用一個數組cur記錄點u以前循環到了哪一條邊,以此來加速
總代碼以下,修改的地方已在代碼中標出:

class Graph
{
private:
    int cnt;
    int Head[maxN];
    int Next[maxM];
    int W[maxM];
    int V[maxM];
    int Depth[maxN];
    int cur[maxN];//cur就是記錄當前點u循環到了哪一條邊
public:
    int s,t;
    void init()
        {
            cnt=-1;
            memset(Head,-1,sizeof(Head));
            memset(Next,-1,sizeof(Next));
        }
    void _Add(int u,int v,int w)
        {
            cnt++;
            Next[cnt]=Head[u];
            Head[u]=cnt;
            V[cnt]=v;
            W[cnt]=w;
        }
    void Add_Edge(int u,int v,int w)
        {
            _Add(u,v,w);
            _Add(v,u,0);
        }
    int dfs(int u,int flow)
        {
            if (u==t)
                return flow;
            for (int& i=cur[u];i!=-1;i=Next[i])//注意這裏的&符號,這樣i增長的同時也能改變cur[u]的值,達到記錄當前弧的目的
            {
                if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))
                {
                    int di=dfs(V[i],min(flow,W[i]));
                    if (di>0)
                    {
                        W[i]-=di;
                        W[i^1]+=di;
                        return di;
                    }
                }
            }
            return 0;
        }
    int bfs()
        {
            queue<int> Q;
            while (!Q.empty())
                Q.pop();
            memset(Depth,0,sizeof(Depth));
            Depth[s]=1;
            Q.push(s);
            do
            {
                int u=Q.front();
                Q.pop();
                for (int i=Head[u];i!=-1;i=Next[i])
                    if ((Depth[V[i]]==0)&&(W[i]>0))
                    {
                        Depth[V[i]]=Depth[u]+1;
                        Q.push(V[i]);
                    }
            }
            while (!Q.empty());
            if (Depth[t]>0)
                return 1;
            return 0;
        }
    int Dinic()
        {
            int Ans=0;
            while (bfs())
            {
                for (int i=1;i<=n;i++)//每一次創建完分層圖後都要把cur置爲每個點的第一條邊 感謝@青衫白敘指出這裏以前的一個疏漏
                    cur[i]=Head[i];
                while (int d=dfs(s,inf))
                {
                    Ans+=d;
                }
            }
            return Ans;
        }
};
相關文章
相關標籤/搜索