網絡流詳解(好文章啊)

網絡流

最近在學習二分圖匹配,網絡流和博弈論(%eazy,miaomiao,lsr_dalao,zyh,zlt),感謝諸位牛犇給蒟蒻的講課,讓我受益匪淺,PPT就不放上來了,有版權問題,下面我給大家談談我近期學習網絡流的心得。(因爲前幾天感冒落了些進度,感謝ergeda和腦屁股的細心輔導)。微笑吐舌頭

一:what is 網絡流???

根據lsr_dalao的ppt上所言:
定義:
圖論中的一種理論與方法,研究網絡上的一類最優化問題 。
很多系統中涉及流量問題,例如公路系統中車流量,網絡中的數據信息流,供油管道的油流量等。我們可以將有向圖進一步理解爲“流網絡”(flow network),並利用這樣的抽象模型求解有關流量的問題

顯然我是有點沒看懂的,那我就用人類聽得懂的話來講講吧。比如我們有一個交通網絡如下圖樣例:
有點醜,不過忍受一下就好。QAQ。我們假設1點是長沙,5點是北京,2,3,4分別是武漢,鄭州,石家莊,假設我們是坐火車北上,長沙到武漢的火車上只能坐兩個人(以此類推),中途不換程,不下車,從長沙出發的雅禮同學們只有多少能到北京呢?很顯然,是4個人。
如下圖:
這裏寫圖片描述
藍色的數字代表火車上最多有幾個人,爲什麼1->3是0?因爲我們要求的是最大的人數,嗯,就是這樣。

二:介紹下網絡流最大流的方法及代碼

依舊先引用lsr_dalao的ppt中話
1.簡介
求解網絡流的基本思想就是每次尋找增廣路(就是源點到匯點的一條可行路)然後ans+=增廣路能流過的流量,更新剩餘網絡,然後再做增廣路,直到做不出增廣路。關於網絡流入門最難理解的地方就是剩餘網絡了....爲什麼在找到一條增廣路後...不僅要將每條邊的可行流量減去增廣路能流過的流量...還要將每條邊的反向弧加上增廣路能流過的流量.?..原因是在做增廣路時可能會阻塞後面的增廣路...或者說做增廣路本來是有個順序才能找完最大流的.....但我們是任意找的...爲了修正...就每次將流量加在了反向弧上...讓後面的流能夠進行自我調整...剩餘網絡的更新(就在原圖上更新就可以了)(what?speak earth language!)反正我是沒看懂的……..若有大神圍觀勿噴。
接下來蒟蒻給你們講講網絡流最大流最簡單也是最慢的一種EK算法
我們設剛剛那個啥交通網絡爲圖G,這個圖是個有向圖,不然做不了,記住這句話,後面有題目。定義c函數爲管道容量大小,就是火車上最多坐多少個人,f函數爲管道的流量,就是火車身上現在做了幾個人。
顯然c函數要大於等於f函數的大小(不多說,水流多了管子會爆,其次,這是中國,不是印度,還是不能坐火車頂上的)。這是圖G的三個性質之一:容量限制,然後有個反對稱性,就是流過去的f = 流回來的-f,現在不懂沒事,一會講反向邊的時候細講,第三個就是流守恆性,就是從源點出發的總流量等於到匯點的總流量,就是從長沙出發的雅禮大佬們不能在火車上失蹤了。
明白了這些,我現在來講網絡流中最難理解東西:反向邊
來個經典的圖:
這裏寫圖片描述

還是很醜,嗯,有向圖吧,不多說了,初始是0號節點流向一號節點和四號節點(未畫出)1->2->5->6->7流量都是10,我們假設這條路徑上都是滿流,就是c = f 就是不能再流其他的流了,然後我們設定其他邊上也是c = 10,4->5也有5的流量,如果按照EK算法中的bfs來說,爲了找到一個最大流,2->3這條邊都不會走,可能一開始找瓶頸把4,5入隊,但是後面不斷調整流量時,流量回被調成10,畢竟程序是爲了尋找最大流,所以說如果不把流過的邊加上一個反向邊4->5這個流量都不會被加入增廣路,可能你還是沒怎麼理解,我先來介紹下殘量網絡,這樣你會理解的更深。
這裏寫圖片描述
紅色的數字代表迴流的量,2->3那條邊我先暫時不標,在殘留網絡的中如果f<c那麼就給它連一條回去的邊,先別問我爲什麼,慢慢來。連回去的邊大小爲增廣路上流量的大小,原來的邊爲c-f,流量爲0的邊不在殘量網絡中出現。
現在我們再來講講反相邊到底是用來幹嘛的,你看,如果給2->5加一條反相邊,是不是4可以通過反相邊到達匯點7,而原來的圖是不行的因爲5,6這條邊已經滿流,所以說,如果不加反相邊,會有一條路都找不到,那不就很尷尬了,^_^。
換句話說,加條反相邊那就是給程序一個反悔的機會,現實中,反相邊是不存在的,只是在程序中出現,實際上,這就相當於4->5的流量轉給2->3,第一條路變成1,2,3,7,第二條路變成4,5,6,7,所以,是不是更好理解了呢?
接下來,我來講講EK的代碼。

//不要太在意代碼風格啦。
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm> 
#include<iostream>
#include<cstdio>
#define For(a,b,c) for(a=b;a<=c;a++)
#include<queue>
#define inf 999999999
using namespace std;
const int maxn = 1010;
int rong[510][510],liu[510][510];
int p[maxn];
int m,n;
int pre[maxn];
int sum;
void internet(){
    queue<int> q;
    while(1){//不斷通過bfs來找增光路,然後ans+=增光路上的流量。
        int i,j;
        memset(p,0,sizeof(p));
        p[1]=inf;//這裏的p數組有兩個作用,一是用來標記是非訪問過,其次是用來記增廣路上的瓶頸。
        q.push(1);
        while(!q.empty()){
            int ans=q.front();
            q.pop();
            For(i,1,n){
                if(!p[i]&&liu[ans][i]<rong[ans][i]){
                    p[i]=min(p[ans],rong[ans][i]-liu[ans][i]);
                    pre[i]=ans;//記錄增廣路。
                    q.push(i);
                }
            }
        }
        if(!p[n]){
            break;//如果n點找不到增光路,說明已經沒增廣路到匯點了。
        }
        sum+=p[n];
        int tmp=n;
        while(pre[tmp]){//不斷調整流量大小。
            liu[pre[tmp]][tmp]+=p[n];
            liu[tmp][pre[tmp]]-=p[n];
            tmp=pre[tmp];
        }
    }
}
int main(){
    int i,j,k;
    int x,y,z;

    while(scanf("%d%d",&m,&n)!=EOF){
        sum=0;
        memset(pre,0,sizeof(pre));
        memset(rong,0,sizeof(rong));
        memset(liu,0,sizeof(liu));
        For(i,1,m){
            scanf("%d%d%d",&x,&y,&z);
            rong[x][y]+=z;
        }
        internet();
        printf("%d\n",sum);
    }
    return 0;
}



嗯,EK,還是很簡單的,接下來我們講講dinic。
dinik是啥捏,就是比EK快的算法,跟二分圖匹配裏的hk算法很像。dinic用到了一個深度標號,是bfs求得的,根據bfs的性質,標號大小是按距離遠近嚴格遞增的,搜索樹中同一層的爲同一標號,如圖,藍色數字爲深度標號:
這裏寫圖片描述
很形象是吧,bfs都不會的話,那還是先別碰網絡流,然後我們按照深度標號嚴格遞增或遞減(看是從源點還是匯點出發)用dfs搜索,然後就好了。那爲什麼?因爲之前講反向邊的時候我將講過,實際上1到5的路徑中,我們現實中肉眼觀察只有1,2,5,和1,3,5這兩條邊中間的邊是不會流的,因爲時加入的反向邊,時間上不存在,如是我們就可以按照標號嚴格遞增或遞減來進行搜素,然後鬆弛即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>

using namespace std;

#define REP(i,a,b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++i)

inline int read()
{
    register int c = getchar(), fg = 1, sum = 0;
    while(c > '9' || c < '0' ) { if(c == '-')fg = -1; c = getchar();}
    while(c <= '9' && c >= '0') {sum = sum *10 + c - '0';c = getchar();}
    return fg * sum;
}
const int maxn = 1010;
int n,m;
int be[maxn], ne[maxn], to[maxn], e = 0, w[maxn]; 
int d[maxn];

void add(int x,int y,int z)
{
    to[e] = y;
    ne[e] = be[x];
    be[x] = e;
    w[e] = z;
    e++;
}


int bfs()
{
    memset(d,-1,sizeof(d));
    queue<int>q;
    q.push(n),d[n] = 0;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();//這裏爲什麼是i!=-1呢?因爲,標號是從0開始的
        for(int i = be[u]; i!=-1; i = ne[i])
        {
            int v = to[i];//這裏的^是很常用的,因爲前向星加邊是兩條一起加的,只是反向邊一開始是爲零的,所以^1一下可以得到另一條邊,比如0^1 = 1,1 ^ 1 = 0, 2^1 = 3,3 ^1 = 2;
            if(w[i ^ 1] && d[v] == -1)
            {
                d[v] = d[u] + 1;
                q.push(v);
            }
        }
    }
    return d[1] != -1 ;
}

int dfs(int x,int low)
{
    if(x == n)return low;//low爲瓶頸。
    int k;
    for(int i = be[x]; i!=-1 ; i = ne[i])
    {
        int v = to[i];
        if(w[i] && d[v] == d[x] - 1 )
        {
            k = dfs(v,min(low,w[i]));
            if(k>0){
                w[i] -= k;
                w[i^1] += k;
                return k;
            }
        }
    }
    return 0;
}

int main()
{
    while(scanf("%d%d",&m,&n)!=EOF)
    {    e = 0;
        memset(be,-1,sizeof(be));
        REP(i,1,m)
        {
            int x,y,z;
            x = read(), y = read(), z = read();
            add(x,y,z);
            add(y,x,0);
        }
        int ans = 0,k;
        while(bfs())
        {
            k = dfs(1,1e7);
            ans += k;
        }
        printf("%d\n",ans);
    }
}

三:費用流

這裏只講最小費用流,只是講EK中的bfs換成了spfa,因爲網絡中的每條邊有了個費用。

/*************************************************************************
    > File Name: poj2195_Going_Home.cpp
    > Author: Drinkwater-cnyali
    > Created Time: 2017/2/12 8:47:55
************************************************************************/

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>


using namespace std;

#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
#define mem(a, b) memset((a), b, sizeof(a))
#define inf 999999999

int read()
{
    int sum = 0, fg = 1; char c = getchar();
    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }
    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }
    return sum * fg;
}

int n,m,num1 = 0,num2 = 0;
int be[100010], ne[100010], to[100010], c[100010], w[100010], e;
char s[200];
int pre[20010],id[20010],p[20010],d[20010];

struct T
{
    int x,y;
}H[10010],hm[10010];

void add(int x,int y,int ci,int wi)
{
    to[e] = y; ne[e] = be[x]; be[x] = e;
    c[e] = ci; w[e] = wi; e++;
    to[e] = x; ne[e] = be[y]; be[y] = e;
    c[e] = 0; w[e] = -wi; e++;
}

bool spfa()
{
    queue<int>q;
    REP(i,0,num1+num2+1)d[i] = inf;
    memset(pre,-1,sizeof(pre));
    memset(id,-1,sizeof(id));
    memset(p,0,sizeof(p));
    q.push(0),d[0] = 0, p[0] = 1;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        p[u] = 0;
        for(int i = be[u]; i != -1 ; i = ne[i])
        {
            int v = to[i];
            if(c[i])
            {
                if(d[v] > d[u] + w[i])
                {
                    d[v] = d[u] + w[i];
                    pre[v] = u;
                    id[v] = i;
                    if(!p[v])
                    {
                        q.push(v);
                        p[v] = 1;
                    }
                }
            }
        }
    }
    return d[num1+num2+1] < inf;
}

int calc()
{
    int sum = 0, flow = inf;
    for(int i = num1+num2+1; pre[i]!= -1; i = pre[i])flow = min(flow, c[id[i]]);
    for(int i = num1+num2+1; pre[i]!= -1; i = pre[i])
    {
        sum+=w[id[i]] * flow;
        c[id[i]] -= flow;
        c[id[i] ^ 1] += flow;
    }
    return sum;
}

int main()
{
    while(1)
    {
        n = read(), m = read();
        if(n == 0 && m == 0)break;
        memset(H,0,sizeof(H));
        memset(hm,0,sizeof(hm));
        memset(be,-1,sizeof(be));
        e = 0;
        num1 =  num2 =0;
        REP(i,1,n)
        {
            scanf("%s",s);  
            REP(j, 0, strlen(s) - 1)
            {
                if(s[j] == 'H')H[++num1].x = i, H[num1].y = j+1;
                if(s[j] == 'm')hm[++num2].x = i,hm[num2].y = j+1;
            }
        }
        REP(i, 1, num1)
            REP(j,1,num2){
                int k = abs(H[i].x-hm[j].x)+abs(H[i].y-hm[j].y);
                add(i,j+num1,1,k);
            }
        REP(i, 1, num1)add(0,i,1,0);
        int k = num1 + num2 + 1;
        REP(i,num1+1,num1+num2)add(i,k,1,0);
        int ans = 0;
        while(spfa())ans += calc();
        printf("%d\n",ans);
    }
    return 0;
}
一個模板,提供借鑑。

今後幾天我會發幾篇網絡流好題的blog,希望大家看了有所收穫。

發佈了55 篇原創文章 · 獲贊 52 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章