1. 程式人生 > >最小費用最大流(詳解+模板)

最小費用最大流(詳解+模板)

By Bartholomew

先感謝大家提出來的寶貴建議 (Thank You!)

建議看的人先 打一下 最大流 的 模板

讓我們來打一篇儘量讓大家都看懂的blog

證明這個演算法其實在最下面會提到的

我們首先告訴大家 這種題目的解法

1.求出當前殘留網路之中 s->t 的最短路 (Bellman_Ford) 因為有可能會有反邊

2.對於答案就是 +=dis[t] × totflow!

3.在處理完了之後就是要記住生成反邊

本人是用 vector 的!

struct edge{ int to,cap,cost,rev;};
int
n,m,flow,s,t,cap,res,cost,from,to; std::vector<edge> G[MAXN_]; int dist[MAXN_],prevv[MAXN_],preve[MAXN_]; inline void add() { G[from].push_back((edge){to,cap,cost,(int)G[to].size()}); G[to].push_back((edge){from,0,-cost,(int)G[from].size()-1}); }

於是終止的條件就是 只要跑到不能跑為止就是答案了!
也就是

dis[t]=INF

(2300 ms)

SPFA完整程式碼:

#pragma GCC optimize(2)
#include <iostream>
#include <algorithm>
#include <map>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN 50050
#define MAXN_ 5050
#define INF 0x3f3f3f3f
using
namespace std; struct edge{ int to,cap,cost,rev;}; int n,m,flow,s,t,cap,res,cost,from,to; std::vector<edge> G[MAXN_]; int dist[MAXN_],prevv[MAXN_],preve[MAXN_]; // 最短路的前驅節點 和 對應的邊 inline void add() { //也許你看到這裡會看不懂,為什麼要存一個 size 進來, 那麼別急,我們下邊的存反邊 會用到的! G[from].push_back((edge){to,cap,cost,(int)G[to].size()}); G[to].push_back((edge){from,0,-cost,(int)G[from].size()-1}); } inline int read() { int x=0; char c=getchar(); bool flag=0; while(c<'0'||c>'9'){if(c=='-')flag=1; c=getchar();} while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();} return flag?-x:x; } inline void min_cost_flow(int s,int t,int f) { while(f > 0) { memset(dist,INF,sizeof dist); dist[s] = 0; bool update = true; while(update) { update = false; for(int i=1;i<=n;i++) { if(dist[i] == INF) continue; for(int j=0;j<(int)G[i].size(); ++j) { edge &e = G[i][j]; if(e.cap > 0 && dist[e.to] > dist[i] + e.cost) { dist[e.to] = dist[i] + e.cost; prevv[e.to] = i; preve[e.to] = j; update = true; } } } } // 此時的 INF 表示再也沒有 增廣路徑,於是就是最後的答案了! if(dist[t] == INF) break; int d = f; for(int v = t; v != s; v = prevv[v]) d = min(d,G[prevv[v]][preve[v]].cap); // d 就是 增廣路的 流量! f -= cap; flow += d; res += d * dist[t]; for(int v=t;v!=s;v=prevv[v]) { edge &e = G[prevv[v]][preve[v]]; e.cap -= d; G[v][e.rev].cap += d; // 給 反邊加上適當的邊權! } } } int main(int argc,char const* argv[]) { n = read(); m = read(); s = read(); t = read(); for(int i=1;i<=m;++i) { scanf("%d%d%d%d",&from,&to,&cap,&cost); add(); } min_cost_flow(s,t,INF); printf("%d %d\n",flow,res); return 0; }

重點程式碼:Primal-Dual 原始對偶演算法(費用流)

思考:如果我們將 SPFA 增廣 改成 Dijstra 會不會更好!

答案是 很穩

(463 ms) 快了 近 1/4

我們如果給每一個節點 附上 h[i]

h_i[v]: f-i 的殘餘網路中 起點 s 到 u 的最短路

在這個定義的基礎上,將 e=(uv) 的邊權變為 d(e)’ = d(e) + h(u) - h(v)

如果合理的選取就是 能夠有

d(e)’ = d(e) + h(u) - h(v) >= 0

又因為最短路性質滿足: dist[v] - dist[u] <= e.cost

那麼我們就讓這個h[i]=dis[i],就可以滿足所有跑的邊權都是非負的了.
那麼現在就是所有的邊都不是 負邊 了 ! 所以就是可以跑 Dijstra

if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to])
{
        // 這個能夠保證 所有的邊權都是 >= 0
        dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
        prevv[e.to] = v;
        preve[e.to] = i;
        D.push(P(dist[e.to],e.to));
}

完整程式碼:

#pragma GCC optimize(2)
#include <iostream>
#include <algorithm>
#include <queue>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN_ 5050
#define INF 0x3f3f3f3f
#define P pair<int,int>
using namespace std;
struct edge{ int to,cap,cost,rev;};
int n,m,flow,s,t,cap,res,cost,from,to,h[MAXN_];
std::vector<edge> G[MAXN_];
int dist[MAXN_],prevv[MAXN_],preve[MAXN_]; // 前驅節點和對應邊
inline void add()
{
    G[from].push_back((edge){to,cap,cost,(int)G[to].size()});
    G[to].push_back((edge){from,0,-cost,(int)G[from].size()-1});
} // 在vector 之中找到邊的位置所在!
inline int read()
{
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}
inline void min_cost_flow(int s,int t,int f)
{
    fill(h+1,h+1+n,0);
    while(f > 0)
    {
        priority_queue<P,vector<P>, greater<P> > D;
        memset(dist,INF,sizeof dist);
        dist[s] = 0; D.push(P(0,s));
        while(!D.empty())
        {
            P now = D.top(); D.pop();
            if(dist[now.second] < now.first) continue;
            int v = now.second;
            for(int i=0;i<(int)G[v].size();++i)
            {
                edge &e = G[v][i];
                if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to])
                {
                    dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
                    prevv[e.to] = v;
                    preve[e.to] = i;
                    D.push(P(dist[e.to],e.to));
                }
            }
        }
        // 無法增廣 , 就是找到了答案了!
        if(dist[t] == INF) break;
        for(int i=1;i<=n;++i) h[i] += dist[i]; 
        int d = f;
        for(int v = t; v != s; v = prevv[v])
            d = min(d,G[prevv[v]][preve[v]].cap);
        f -= d; flow += d;
        res += d * h[t];
        for(int v=t;v!=s;v=prevv[v])
        {
            edge &e = G[prevv[v]][preve[v]];
            e.cap -= d;
            G[v][e.rev].cap += d;
        }
    }
}
int main(int argc,char const* argv[])
{
   n = read(); m = read(); s = read(); t = read();
   for(int i=1;i<=m;++i)
   {
           from = read(); to = read(); cap = read(); cost = read();
           add();
   }
   min_cost_flow(s,t,INF);
   printf("%d %d\n",flow,res);
   return 0;
}

其實大家可能最疑惑的就是為什麼有程式碼是 :

   for(int i=1;i<=n;++i) h[i] += dist[i];

其實是這樣的:

**我們一條 s->i 的路 : 其實就是
dist[i] = sigma(e.cost) + h(s) - h(i) // 因為我們之間的所有的邊的勢的加減其實都是可以抵消的!**

那麼我們一直知道 h[s] 一直都是 0 ,因為 它的最短路都是 0 !

那麼在裸的用 Bellman_Ford 的時候我們就是 用的是sigma(e.cost) 才是我們要的最短路dist[i] , 所以 h[i]’ = h[i]+dist[i]

所以就是 一直都是 “+=”

那麼也許還會有大佬會想到在增廣了之後會建立反邊,那麼反邊按照道理來說會有:
e = e+h[v]h[u] 恰好就是 (e+h[u]h[v]) 既然原來邊權範圍是>=0的,現在就是是