【Luogu P3376】網絡最大流

Luogu P3376
最大流是網絡流模型的一個基礎問題。
網絡流模型就是一種特殊的有向圖。
概念:算法

  • 源點:提供流的節點(入度爲0),類比成爲一個無限放水的水廠
  • 匯點:接受流的節點(出度爲0),類比成爲一個無限收水的小區
  • 弧:類比爲水管
  • 弧的容量:類比爲水管的容量;用函數\(c(x,y)\)表示弧\((x,y)\)的容量
  • 弧的流量:類比爲當前在水管中水的量;用函數\(f(x,y)\)表示弧\((x,y)\)的流量
  • 弧的殘量:即容量-流量
  • 容量網絡:對於一個網絡流模型,每一條弧都給出了容量,則構成一個容量網絡。
  • 流量網絡:對於一個網絡流模型,每一條弧都給出了流量,則構成一個流量網絡。
  • 殘量網絡:對於一個網絡流模型,每一條弧都給出了殘量,則構成一個殘量網絡。最初的殘量網絡就是容量網絡。

對於網絡流模型\(G=(V,E)\)\(V\)爲點集,\(E\)爲邊集)有以下性質:網絡

  • 流量守恆:除了源點與匯點以外,流入任何節點的流必定等於流出該節點的流
  • 容量限制:\(\forall (x,y) \in E,有0<=f(x,y)<=c(x,y)\)
  • 斜對稱性:\(\forall (x,y) \in E,有f(x,y)=-f(y,x).\)相似於函數奇偶性中的奇函數,或者是矢量的方向。

最大流問題,用通俗的方式解釋就是從源點S到匯點T輸送流量,詢問最多有多少流量能輸送到匯點。
對於這樣的問題,咱們引入一些新概念:函數

  • 增廣路:一條從源點到匯點的路徑\(R\),知足\(\forall (x,y) \in S, c(x,y)-f(x,y)>=0.\)即殘量非負
  • 最大流最小割定理:網絡流模型達到最大流,當且僅當殘量網絡中沒有任何增廣路(並不完整,可是足夠了)

\(Ford-Fulkerson\)方法:每一次尋找一條增廣路徑。根據木桶原理,該增廣路的最大流量\(f_{max}<=min(c(x,y)-f(x,y))\)。據此從源點發送流量至匯點並修改路徑上全部弧的殘量,直到沒法找到增廣路爲止。
\(Edmons-Karp\)算法:基於\(Ford-Fulkerson\)方法的一種算法,核心就是利用\(BFS\)搜索源點到匯點的最短增廣路,根據\(Ford-Fulkerson\)方法修改殘量網絡。複雜度最壞是\(O(nm^2)\)
因此其實咱們在求最大流相關問題時,其實只利用到了殘量網絡,流量和容量通常並不須要記錄。
關鍵點:有時候在求最大流時咱們可能須要縮減一條邊的流量,因此咱們引入了反向邊。當咱們選用了一條反向邊時,至關於縮減正向邊的流量。很容易發現反向邊的殘量等於正向邊的流量(最多剛好抵消正向流量)。
這樣就能保證算法的正確性。spa

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
struct data
{
    int to,next,val;
}e[2*100005];
int cnt,head[10005],prep[10005],pree[10005],flow[10005],ans;
queue<int> que;
int n,m,s,t,u,v,w;
void add(int u,int v,int w)
{
    e[++cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
    e[cnt].val=w;
}
int bfs(int s,int t)
{
    while (!que.empty()) que.pop();
    flow[s]=0x3f3f3f3f;//flow記錄的是在增廣路上通過該點的流量
    que.push(s);
    for (int i=1;i<=n;i++) 
    {
        prep[i]=-1;//用於記錄前驅節點
        pree[i]=0;//用於記錄前驅邊的編號
    }
    prep[s]=0;
    while (!que.empty())
    {
        int now=que.front();
        que.pop();
        if (now==t) break;
        for (int i=head[now];i;i=e[i].next)
        {
            if (e[i].val>0&&prep[e[i].to]==-1)
            {
                que.push(e[i].to);
                flow[e[i].to]=min(flow[now],e[i].val);
                pree[e[i].to]=i;
                prep[e[i].to]=now;
            }
        }
    }
    if (prep[t]!=-1) return flow[t];
    else return -1;
}
void EK(int s,int t)
{
    int delta=bfs(s,t);//尋找最短增廣路的最大流量
    while (delta!=-1)
    {
        ans+=delta;
        for (int j=t;j;j=prep[j])
        {
            e[pree[j]].val-=delta;
            e[pree[j]^1].val+=delta;
            //鏈式前向星存邊從編號2開始存儲能夠經過異或1快速取得反向邊的編號。
        }
        delta=bfs(s,t);
    }
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    cnt=1;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(v,u,0);
        add(u,v,w);
        //加入正反邊
    }
    EK(s,t);
    printf("%d",ans);
    return 0;
}
相關文章
相關標籤/搜索