最大網絡流——增廣路算法


 

幾句廢話:
讀了劉汝佳的書之后,感覺一切都是那么茫然,於是自己在網上找教程,自己一點點碼的,大概用了三天。
網絡流基礎:
看來我很有必要說一下網絡流的基礎
網絡流問題就是給你一個圖,每個圖的邊權叫做這條邊的流量,問你從起始點出發,有多少值能通過這些邊流到重點
我知道你沒看懂,舉個例子:

如圖:

最大值為

從1到2到4運6個

從1到2到3到4運1個

從1到3到4運3個

一共運10個。

舉例說完了,那么我說幾個定義:

容量,就只一條邊的權值,表示能從這條邊運送的最大值

流量,表示一條邊實際上流過的最大值

那么,說算法的時間到了,還是先上數據

(數據為上圖所示)

4 5//四個點,五條邊
1 2 8
1 3 3
2 3 1
2 4 6
3 4 5

開始,我們假設所有邊的流量都是0

以這個數據,這樣可以達到。

於是我們試圖增加一些變得流量,使得重點的流量更大。

那么如何增加?就需要我們找増廣路。

什么是増廣路,就是一條從起點,到終點的一條每條邊容量-實際流量>的路0。

比如

0/8表示一條邊流量為0,容量為8

現在很明顯可以看出,存在一條増廣路1——2——4

然后怎么辦,把這條路上的每條邊流量都加上每條邊容量與流量差的最小值

例如8-0>6-0 因此1——2、2——4這兩條邊的流量都加上6,並且答案也加上6

然后我們繼續找増廣路,又發現一條1——2——3——4

還是按剛才的,找出最小值,為1,所以這條增光路上每一邊流量加1

然后ans再加一變成7

然而我們繼續找増廣路,發現1——3——4

按照剛才,流量加上2,變成

ans加上3變成10

然后發現沒有増廣路了,於是算法結束,答案是10.

等等,這就要上代碼了?傳說中得noi算法網絡流就這么簡單?不存在的

細心的同學(滑稽)會發現,這種情況就很神奇

你可能回算1——2——3——4這條増廣路,於是答案是1,但是事實證明最優答案明顯是2,那么問題出在哪里?

因為我們沒有給予返回的機會,也就是相當於第一次找到的不是最優解,那么怎么辦?

所以,我們要有一個反向邊,來給程序反悔的機會,每條邊都創建一條反向邊,反向邊的初始容量是0,流量都為負數。

假設1——2這條邊本來權值是1,流量也是1,那么他的反向邊的容量是0,流量是-1,這個應該好理解。

上圖就會變為下圖:

 

然后以剛才舉例,當確定増廣路1——2——3——4后,圖是這樣的:

 

於是,我們又找到了一條新的増廣路:1——3——2——4

(因為0-(-1)=1,所以這條邊也可以走)

那么答案是1+1=2

那么問題來了,為什么這樣就算一種呢?

因為制造相反邊,如2——3,就是相當於吧原來從而流到3的量流回來了。

這樣就可以求出最大值。

口胡內容就到這里,其實我寫的不是很清楚,大家看劉汝佳的可能會更清楚一些,但是重點來了!!!

這道題代碼實現非常之困難,至少對於我來說,所以劉汝佳的代碼我壓根沒看懂。

因此,我自己寫得一份淺顯易懂的代碼

沒有vector!!!沒有指針!!!

而且有復雜的注釋!!!

#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <queue>
using namespace std;
int n,m;//n個點,m條邊 
int head[100010];//這是鄰接表的標志,head[i]表示以i為頂點的第一條邊 
struct edge
{
    int cap,flow;//cap為容量,flow為流量 
    int from,to;//一條邊的起點終點 
    int next;//和這條邊起點相同的下一條邊(鄰接表標志) 
}map[100010];
int index=-1;//鄰接表輸入數組 
int flag=0;//退出的標記 
void build_edge(int a,int b,int c)//構造鄰接表,插入連接表 
{
    index++;
    map[index].from=a;
    map[index].to=b;
    map[index].cap=c;
    map[index].flow=0;
    map[index].next=head[a];
    head[a]=index;
    index++;
    map[index].from=b;//插入相反邊 
    map[index].to=a;
    map[index].cap=0;
    map[index].flow=0;
    map[index].next=head[b];
    head[b]=index;
    return ;
}
int bfs()//運用bfs找到増廣路 
{
    int min_flow[100010];//min_flow[i]代表到第i好點時,當前所走過變的容量-流量的最小值 
    memset(min_flow,0,sizeof(min_flow));
    queue<int> Q;//用隊列 維護 
    min_flow[1]=999999;//開始為無限大 
    int p[100010];//這個很重要,構造增光路時,記錄當前點是由那條邊找到的,以此找到増廣路時能從終點以此到起點的邊的流量加上最小值 
    p[1]=-1;
    Q.push(1);//起點入隊 
    while(!Q.empty())
    {
        int now=Q.front();
        Q.pop();
        for(int e=head[now];e!=-1;e=map[e].next)//鄰接表枚舉當前點的所有連邊 
        {
            int v=map[e].cap-map[e].flow;//v就是為當前邊容量-流量 
            if(v>0 && !min_flow[map[e].to])//當v>0 並且這個點沒有訪問過時 
            {
                p[map[e].to]=e;//記錄到達這個點的邊的序號 
                min_flow[map[e].to]=min(min_flow[now],v);//維護最小值 
                Q.push(map[e].to);//維護bfs 
            }
        }
        if(min_flow[n]!=0)//如果終點得到更新,說明找到増廣路,直接break 
            break;
    }
    if(min_flow[n]==0)//當沒有更新到終點,也就是重點最小增加值為0時,說明沒有増廣路了,直接完事 
        flag=1;
    for(int e=n;;e=map[p[e]].from)//從終點開始,以此倒着走増廣路,把沿途上邊的流量都加上終點最小更新值 
    {
        if(p[e]==-1)//如果到頭就完事 
            break;
         map[p[e]].flow+=min_flow[n];//流量加上最小更新至 
         map[p[e]^1].flow-=min_flow[n];//反向邊減去 
    }
    return min_flow[n];//返回最小更新至,加到答案當中 
}
int max_flow()
{
    int flow=0;//這就是神聖的答案 
    while(1)
    {
        flow+=bfs();//循環搜索 
        if(flag==1)//沒有増廣路,就直接跳出 
            break;
    }
    return flow;//返回答案 
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<=m;i++)//初始化,很重要,制勝之點 
    {
        map[i].next=-1; 
        head[i]=-1;
    }
    for(int i=1;i<=m;i++)//讀入,構造鄰接表 
    {
        int a,b,c,d;
        cin>>a>>b>>c;
        build_edge(a,b,c);
    }
    cout<<max_flow();//輸出答案 
}
/*
測試數據 
4 4
1 2 2
2 4 2
1 3 3
3 4 1
*/

學信息不易,作業還沒動,求關注!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM