流問題Flow Problem(網絡最大流)- HDU 3549

        網絡最大流問題屬於算法 裏面較難的問題,因爲牽涉的概念比較多,這一篇可能需要你花比較多的時間去理解,除了看這個,最好能多參考別的書籍或者文章進行比較學習,不然可能容易產生理解的偏差。

 

        另外本公衆號並不打算講解過於複雜的問題,網絡流問題已經嚴重超過了最初設想的五分鐘限制,不過由於是第一篇網絡流的題目,可以作爲後面相關問題的基礎,姑且多寫點。如果你覺得一次難以看懂,可以在時間多的時候看看。

 

        網絡流,顧名思義,可以認爲是網絡通信的流量,也可以想象成水管裏水的流動情況,存在節點和邊,每條邊有容量且不一樣(管道大小不一)。

 

        最大網絡流就是要尋找從節點s到節點t的能夠取得的最大流量。

 

        現在我們來理解網絡最大流算法。前方高能,信息量會比較大

 

        1、容量網絡:定義圖G是一個有向圖(網絡),對於每一條邊,有一個權重c,這個權重c表示這條邊的容量capacity。

 

        2、對於上圖,要找出從s到t的最大網絡流,首先我們看幾個定義。

 

        3、流f:流f是指從s到t一條路徑上的流量,可以看成是一條水流。

 

        4、殘存網絡:對於G,減去一條流f後的網絡,網絡G在這條路徑上的邊容量需要減去流f的值。也就是說這個路徑被流f佔用了。

 

        5、增廣路徑:給定流網絡G和流f,增廣路徑是指殘存網絡中一條從源結點s 到匯點t的簡單路徑(路徑中不存在重複的頂點或邊 )。 簡單的說就是一條從s到t的路徑。對於上圖中s-v1-v3-t就是一條增廣路徑。

 

        6、反向流量:反向流量初始爲0,每次減去一條增廣路徑後,我們要在邊信息上加上反向流量值,反向流量的作用比較微妙,如圖:

        對於增廣路徑1-2-3-4,從G中減去之後,1-2、2-3、3-4都爲0,這時候就找不到增廣路徑了,加上反向流量後:

        這時候就能找到1-3-2-4這條增廣路徑了。

 

7、層次:在剩餘圖中, 我們把從源點到點 u 的最短路徑長度稱作點 u 的層次, 記爲level(u)。 源點s的層次爲 0。直觀地講, 層次圖是建立在剩餘圖基礎之上的一張“最短路圖”。 從源點開始,在層次圖中沿着邊不管怎麼走,經過的路徑一定是終點在剩餘圖中的最短路。

 

算法思路整理:

1、每次迭代採用廣度優先計算當前G的層次屬性;

2、採用深度優先的方式來搜索所有的路徑並計算出流f的值;

3、更新G(減去增廣路徑,計算反向流量);

4、如果找不到增廣路徑,則當前流的和就是最大流。

 

Problem Description

Network flow is a well-known difficult problem for ACMers. Given a graph, your task is to find out the maximum flow for the weighted directed graph.

 對於Acmer來說,網絡流是出名的難。給定一個圖,找出最大的流。

 

Input

The first line of input contains an integer T, denoting the number of test cases.

For each test case, the first line contains two integers N and M, denoting the number of vertexes and edges in the graph. (2 <= N <= 15, 0 <= M <= 1000)

Next M lines, each line contains three integers X, Y and C, there is an edge from X to Y and the capacity of it is C. (1 <= X, Y <= N, 1 <= C <= 1000)

 第一行輸入包含一個整數T,表示有T個測試用例。

對於每個測試用例,第一行是整數N和M,分別表示定點數和邊的個數。下面有M行,每行是三個整數X/Y/C,其中X/Y表示從X到Y,C是容量(權重)。

 

Output

For each test cases, you should output the maximum flow from source 1 to sink N.

 對於每個測試用例,輸出最大從節點1到N的最大流。

 

Sample Input

2

3 2

1 2 1

2 3 1

3 3

1 2 1

2 3 1

1 3 1

 

 

Sample Output

Case 1: 1

Case 2: 2

 

解題思路:套用最大流算法思路。

 

        最大流的理解比較花時間,每個概念都可能產生誤解,建議一邊看代碼一邊理解最大流的算法思想。

 

源碼:G++ 156ms

#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<iostream>
#include<vector>
#include<cstring>
#include<queue>

using namespace std;

#define INFINITE 10000000

const int maxn = 1000;

//邊信息
typedef struct edge_s {
    int to;        //下一個節點
    int cap;    //容量capacity
    int reverse; //反向節點
}edge_t;

//所有的邊信息
vector<edge_t> G[maxn];

//層次信息
int level[maxn];
//迭代信息
int iter[maxn];


void add_edge(int from, int to, int cap) {
    //正向圖
    G[from].push_back({to, cap, (int)G[to].size() });
    //反向圖
    G[to].push_back({from, 0, (int)G[from].size() - 1});
}

//構造分層圖O(E),最多進行V-1次
//s是開始,level[u]是指從起點s到點u的最短路徑長度
//廣度優先搜索使用隊列完成搜索樹的遍歷,層層遞進計算長度
void bfs(int start) {
    //每次迭代,重置level中所有的值爲-1
    memset(level, -1, sizeof(level));
    //隊列
    queue<int > bfs_queue;
    //開始節點的level設置爲0
    level[start] = 0;
    //放入開始節點
    bfs_queue.push(start);

    while (!bfs_queue.empty()) {
        //取出頭結點
        int from = bfs_queue.front();
        bfs_queue.pop();

        int size = G[from].size();

        for (int i = 0; i < G[from].size(); i++) {
            //對於from表,取出
            edge_t &e = G[from][i];
            //e表示邊信息
            if (e.cap > 0 && level[e.to] < 0) {
                //每發現一個下級節點,層次+1
                level[e.to] = level[from] + 1;
                //放入隊列繼續
                bfs_queue.push(e.to);
            }
        }
    }
}

//深度優先搜索,尋找最短增廣路徑
int dfs(int x, int t, int f) {
    //如果起點與終點相同,搜索到終點了,返回這條路徑上的最小值
    if (x == t)
        return f;
    //注意這裏的i是引用,能夠改變iter[x]的值
    for (int &i = iter[x]; i < G[x].size(); i++) {
        edge_t &e = G[x][i];

        //只會往深處尋找level
        if (e.cap > 0 && level[x] < level[e.to]) {
            //接着從下一個節點向t搜索
            //取得較小值,通過遞歸搜索,可以遍歷所有的路徑,遍歷的過程中更新所有邊的最小值
            int d = dfs(e.to, t, min(f, e.cap));

            if (d > 0) {
                //cap減1
                e.cap -= d;
                //反向邊加上減去的流量
                G[e.to][e.reverse].cap += d;
                return d;
            }
        }
    }
    return 0;
}

//s是開始,t是結束
int max_flow(int s, int t) {
    int flow = 0;

    for (;;) {
        //廣度優先,計算層次信息
        bfs(s);
        //如果level小於0,說明找不到路徑
        if (level[t] < 0) return flow;
        //設置iter爲0
        memset(iter, 0, sizeof(iter));
        int f;
        //深度優先,f大於0表示存在路徑
        while ((f = dfs(s, t, INFINITE)) > 0)
            flow += f;
    }

    return flow;
}

int main()
{
    int n, m, Case = 0;
    int T;
    scanf("%d", &T);

    while (T--) {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) G[i].clear();

        //依次輸入邊信息
        for (int i = 1; i <= m; i++) {
            int x, y, cap;
            scanf("%d%d%d", &x, &y, &cap);
            add_edge(x, y, cap);
        }

        //從節點1到n
        int ans = max_flow(1, n);

        printf("Case %d: %d\n", ++Case, ans);
    }

    return 0;
}

個人公衆號:ACM算法日常

專注於基礎算法的研究工作,深入解析ACM算法題,五分鐘閱讀,輕鬆理解每一行源代碼。內容涉及算法、C/C++、機器學習等。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章