1. 程式人生 > >hdu 3549 Flow Problem (最大流 EK)

hdu 3549 Flow Problem (最大流 EK)

Flow Problem

Time Limit: 5000/5000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 22504    Accepted Submission(s): 10516  

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.

 

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)

 

Output

For each test cases, you should output the maximum flow from source 1 to sink 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

 

Author

HyperHexagon

 

Source

題意:網路流問題,用一個有向圖來表示網路結構,有n個點,m條邊,這些邊上有一個權值表示的是這條邊最多能夠通過的流量,現在 在1點傳輸流量,求n點最多能夠得到的流量。

思路:假設經過邊e的流量為f(e),邊e的容量為c(e),流量始終是守恆的,從源點出發的流量=等於終點收到的流量。(好比你要灌溉農田,你從水源點引水到田裡,源點出發的所有水都會到達目標點,既不會增加也不會減少)。流經邊e的流量:f(e)=min(flow,c(e))(總不能比你的容量還大吧)。我們畫出一個網路流圖。

我們常規思路考慮,首先找到第一條能夠從源點到達目標點的路徑(這樣的路徑我們稱為增廣路,因為最初圖的流量是0,現在增加了圖中的流量),然後使得這條路徑流經的流量儘可能的大。然後又找第二條增廣路,然後找第三條這增廣路,然後找第四條增廣路。。。直到圖中沒有增廣路了。

從圖中得知我們能夠沿著1->2->3->5的路徑流過5的流量

然後沿著1->2->4->5的路徑流過5的流量

這時圖中找不出增廣路了,那麼我們圖中一共有10的流量,答案就應該是10嗎?

正確答案不是10,而是11。

從1->2->4->5流經6流量,從1->2->3->5流經5流量,從1->3->5流經1流量。總共答案是11.

為什麼會出現這樣的情況呢?原因是我們分配的不合理,前面的流量分配固定了,後面的流量分配收到限制。解決的辦法是,給每一條邊都增加一條反向邊,程式走反向邊,意思是這條路的流量從新分配,相當於給了程式一個反悔的機會,以便最大化的分配流量。

求最大流的方法有很多種如Dinic,Edmonds-Karp、Ford-Fulkerson以及Shortest Augmenting Paths.

我使用的是Edmonds-Karp,它的效率不高適合資料較小。演算法的核心就是通過BFS不斷尋找圖中的增廣路進行增廣。因為有反向邊,所以能夠使圖中的流量最大化。

AC程式碼:

#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 20;
const int MAXM = 1005;
struct Edge{
    int next,v,w;
}edge[MAXM<<1];

int cnt;
int head[MAXN],pre[MAXN],rec[MAXN],flow[MAXN];
queue<int> q;

void add(int u,int v,int w){
    edge[cnt]=(Edge){head[u],v,w};
    head[u]=cnt++;
    //反向邊
    edge[cnt]=(Edge){head[v],u,0};
    head[v]=cnt++;
}

int bfs(int s,int t){
    memset(pre,-1,sizeof(pre));
    while(!q.empty())
        q.pop();
    pre[s]=s;flow[s]=INF;
    q.push(s);
    while(!q.empty()){
        int top = q.front();q.pop();
        for(int i=head[top];~i;i=edge[i].next){
            int v=edge[i].v;
            if(pre[v]==-1&&edge[i].w>0){
                //由top點流向v點的流量=來自於top點的流量和這條管道的容量取一個較小的值。
                flow[v]=min(flow[top],edge[i].w);
                //記錄增廣路
                pre[v]=top;
                rec[v]=i;
                q.push(v);
            }
        }
        if(pre[t]!=-1) return flow[t];
    }
    return -1;
}

//源點與目標點
int EK(int s,int t){
    //tmp是不斷增加的流
    int ans=0,tmp;
    //不斷的增加流量直到找不到增廣路
    while((tmp=bfs(s,t))!=-1){
        ans+=tmp;
        int k=t;
        while(k!=s){
            edge[rec[k]].w-=tmp;//正向邊
            edge[rec[k]^1].w+=tmp;//反向邊
            k=pre[k];
        }
    }
    return ans;
}

int main(){
    int n,m,T,cae=0;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        cnt=0;
        memset(head,-1,sizeof(head));
        for(int i=0;i<m;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
        }
        printf("Case %d: %d\n",++cae,EK(1,n));
    }
    return 0;
}