前言:在最大流問題的網絡中,給邊新加上費用,求流量為F時費用的最小值。該類問題就是最小流費用問題。
算法分析:在解決最小費用流的問題上,我們將沿着最短路增廣並以費用作為路徑長短的衡量,在增廣時殘余網絡中的反向邊的費用應該是原邊費用的相反數,目的是保證過程可逆並且正確。因此在本算法的實現上,其一我們需要利用Bellman_Ford或Dijkstra算法求得最短路並將其保存,其二則是求解該通過該最短路的實際流量並且修改相關數據,以便下一次在殘余網絡上繼續增廣。如果采用Bellman_Ford求最短路,時間復雜度為O(FVE);如果采用Dijkstra,時間復雜度為O(FElogV)。
算法正確性的證明:
(1)設通過上述算法求得的費用流為f並假設存在流量相同但費用更小的流f'。已知流f和f'中,除了s與t以外,其它頂點的流入量均等於流出量。則在流f'—f中,由於f與f'的流量相同且f'的費用小於f,所以流f'—f是由若干圈組成的且至少存在一個負圈。那么我們可以得到這樣一個結論:如果f是最小費用流,則網絡中沒有負圈,反之亦成立。
(2)利用(1)中的結論,我們將通過歸納法證明。設f(i)是流量為i的所有流中費用最小的流,則當i=0時,f(i)=0,對應的殘余網絡便是原圖,如果原圖不含負圈則f(0)便是流量為0的最小費用流。假設流量為i時,f(i)是最小費用流,並且已經通過算法求得流量為i+1時的費用為f(i+1),則f(i+1)—f(i)是f(i)對應的殘余網絡中s-t的最短路。
(3)假設f(i+1)不是最小費用流,即存在費用更小的流f'(i+1)。在流f'(i+1)—f(i)中,除s和t以外,其它頂點的流入量均等於流出量,因而是一條s到t的路徑和若干圈組成的。已知
f(i+1)—f(i)是f(i)對應的殘余網絡中s-t的最短路,而f'(i+1)費用比f(i+1)更小,所以f'(i+1)—f(i)中至少含有一個負圈即f(i)對應的殘余網絡中含有一個負圈。此結果與f(i)是最小費用流矛盾,故假設不成立即f(i+1)是最小費用流。
最小費用流算法實現:
//Bellman—Ford算法
//Bellman算法求最短增廣路&最小費用流 O(FEV)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define MV 100
#define INF (1 << 20)
struct edge
{
int t, cap, cost, rev;
edge(int to = 0, int c = 0, int ct = 0, int r = 0): t(to), cap(c), cost(ct), rev(r) {};
};
vector <edge> G[MV];//圖的鄰接表表示
int dis[MV];//單源點s到其它頂點的最短距離(本次搜索的最小費用)
int prevv[MV], preve[MV];//最短路中的前驅結點 對應邊
int min_cost_flow(int n, int v, int s, int t, int f)
{
int ans = 0, i, j; //最小費用
while(f > 0)
{
fill(dis, dis + v, INF);
dis[s] = 0
bool update = true;
while(update)
{//bellman算法求解s-t可達路徑中的最短路(費用最少)且是否可達由e.c是否大於0決定
update = false;
for(i = 0; i < v; ++i)
{
int size = G[i].size();
if(dis[i] == INF)
continue;
for(j = 0; j < size; ++j)
{
edge &es = G[i][j];
if(es.cap > 0 && dis[es.t] > dis[i] + es.cost)
{
dis[es.t] = dis[i] + es.cost;
prevv[es.t] = i;//路徑還原
preve[es.t] = j;
update = true;
}
}
}
}
if(dis[t] == INF)
return -1;
int d = f; //d:本次求得的最小費用流
for(i = t; i != s; i = prevv[i])
d = min(d, G[prevv[i]][preve[i]].cap);
ans += d * dis[t];
f -= d;
for(i = t; i != s; i = prevv[i])
{
edge &es = G[prevv[i]][preve[i]];
es.cap -= d;
G[es.t][es.rev].cap += d;
}
}
return ans;
}
void solve(int n, int v, int s, int t, int f)
{
int i, j;
for(i = 0; i < n; ++i)
{//建圖
int s1, t1, cap, cost;
cin >> s1 >> t1 >> cap >> cost;
G[s1].push_back(edge(t1, cap, cost, G[t1].size()));
G[t1].push_back(edge(s1, 0, -cost, G[s1].size() - 1));
}
cout << min_cost_flow(n, v, s, t, f) << endl;
}
int main()
{
int n, v, s, t, f;//n條邊 v個頂點 源點s 匯點t 傳輸的流量f
cin >> n >> v >> s >> t >> f;
solve(n, v, s, t, f);
return 0;
}
//最小費用流Dijkstra算法
//Dijkstra算法求最小費用流核心代碼:
//h[MAX_V]:導入勢保證所有邊均為非負邊 O(FElogV)
int min_cost_flow(int n, int v, int s, int t, int f)
{
int i, ans = 0;
fill(h, h + v, 0);
while(f > 0)
{
//Dijkstra算法:尋找最短路 O(ElogV)
priority_queue<P, vector<P>, greater<P> > que;
fill(dis, dis + v, INF);
dis[0] = 0;
que.push(P(0, s));
while(!que.empty())
{
P p = que.top();
que.pop();
int v = p.second;
if(dis[v] < p.first)
continue;
int size = G[v].size();
for(i = 0; i < size; ++i)
{
edge es = G[v][i];//****
if(es.cap > 0 && dis[es.to] > dis[v] + es.cost + h[v] - h[es.to])
{
dis[es.to] = dis[v] + es.cost + h[v] - h[es.to];
prevv[es.to] = v;
preve[es.to] = i;
que.push(P(dis[es.to], es.to));
}
}
}
if(dis[t] == INF)
return -1;
//更新勢
for(i = 0; i < v; ++i)
h[i] += dis[i];
int d = f;
for(i = t; i != s; i = prevv[i])
d = min(d, G[prevv[i]][preve[i]].cap);
ans += d * h[t];
f -= d;
for(i = t; i != s; i = prevv[i])
{
edge &es = G[prevv[i]][preve[i]];
es.cap -= d;
G[i][es.rev].cap += d;
}
}
return ans;
}
