最小費用最大流(詳解+模板)
By Bartholomew
先感謝大家提出來的寶貴建議 (Thank You!)
建議看的人先 打一下 最大流 的 模板
讓我們來打一篇儘量讓大家都看懂的blog
證明這個演算法其實在最下面會提到的
我們首先告訴大家 這種題目的解法 :
.求出當前殘留網路之中 -> 的最短路 (Bellman_Ford) 因為有可能會有反邊
.對於答案就是 += × !
.在處理完了之後就是要記住生成反邊!
本人是用 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});
}
於是終止的條件就是 只要跑到不能跑為止就是答案了!
也就是!
(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) 快了 近
我們如果給每一個節點 附上 勢 :
h_i[v]: f-i 的殘餘網路中 起點 s 到 u 的最短路
在這個定義的基礎上,將 =(,) 的邊權變為 ’ = + -
如果合理的選取就是 能夠有
()’ = () + () - () >=
又因為最短路性質滿足: - <=
那麼我們就讓這個,就可以滿足所有跑的邊權都是非負的了.
那麼現在就是所有的邊都不是 負邊 了 ! 所以就是可以跑 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) // 因為我們之間的所有的邊的勢的加減其實都是可以抵消的!**
那麼我們一直知道 一直都是 0 ,因為 它的最短路都是 0 !
那麼在裸的用 Bellman_Ford 的時候我們就是 用的是sigma(e.cost) 才是我們要的最短路 , 所以 ’ = ;
所以就是 一直都是 “+=”
那麼也許還會有大佬會想到在增廣了之後會建立反邊,那麼反邊按照道理來說會有:
= 恰好就是 既然原來邊權範圍是的,現在就是是