您的位置:首页 > 理论基础 > 计算机网络

网络流学习--费用流

2018-08-28 15:53 316 查看
上文谈到 网络流-最大流 问题。

现在我们来学习 网络流--费用流 这一块,有纰漏的地方还请指出哦。

本文涉及的内容:

最大费用最大流

最小费用最大流

本文主要涉及的算法:

SPFA求费用流

dijkstra求费用流

zkw费用流

不明白最大流的读者可以先去了解了解

内容不会太难,毕竟作者能力有限,希望大家能有收获。

费用流,也叫作最小费用最大流,是指在普通的网络流图中,每条边的流量都有一个单价,求出一组可行解,使得在满足它是最大流的情况下,总的费用最小。

首先开始最最小费用流:

最小费用最大流

顾名思义,就是利用最少的花费,达到流量最大的目的。

问题引入:

你是一个工厂的老板,达到可以制造任何多的货物。众所周知工厂不会见在市区,所以你需要运输到市区,从工厂到销售点有若干车次,每车次都有一个起点,终点(单向边),还有一个容量。如今通常按劳分配,司机们取消了固定工资,取而代之的是运费,现在每辆车还有一个单位运费,指你运一单位货物需要支付的钱,问当你满足最大货物运输量的同时最少支出多少钱?

int MCMF(int S, int T)
{
int ans = 0;
while (SPFA(S, T))
{
int minv = 0x7fffffff;
for (int x = T; x != S; x = from[fa[x]])
minv = min(minv, ret[fa[x]]);
ans += minv * dis[T];
for (int x = T; x != S; x = from[fa[x]])
{
ret[fa[x]] -= minv;
ret[rev[fa[x]]] += minv;
}
for (int x = 1; x <= n; x++)
h[x] = dis[x];
}
return ans;
}




#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;
}


zkw算法[转]

最小费用流在 OI 竞赛中应当算是比较偏门的内容, 但是 NOI2008 中 employee 的突然出现确实让许多人包括 zkw 自己措手不及. 可怜的 zkw 当时想出了最小费用流模型, 可是他从来没有实现过, 所以不敢写, 此题 0 分. zkw 现在对费用流的心得是: 虽然理论上难, 但是写一个能 AC 题的费用流还算简单. 先贴一个我写的 costflow 程序: 只有不到 70 行, 费用流比最大流还好写~。

#include <cstdio>
#include <cstring>
using namespace std;
const int maxint=~0U>>1;

int n,m,pi1,cost=0;
bool v[550];
struct etype
{
int t,c,u;
etype *next,*pair;
etype() {}
etype(int t_,int c_,int u_,etype* next_):
t(t_),c(c_),u(u_),next(next_) {}
void* operator new(unsigned,void* p)
{
return p;
}
} *e[550];

int aug(int no,int m)
{
if(no==n)return cost+=pi1*m,m;
v[no]=true;
int l=m;
for(etype *i=e[no]; i; i=i->next)
if(i->u && !i->c && !v[i->t])
{
int d=aug(i->t,l<i->u?l:i->u);
i->u-=d,i->pair->u+=d,l-=d;
if(!l)return m;
}
return m-l;
}

bool modlabel()
{
int d=maxint;
for(int i=1; i<=n; ++i)if(v[i])
for(etype *j=e[i]; j; j=j->next)
if(j->u && !v[j->t] && j->c<d)d=j->c;
if(d==maxint)return false;
for(int i=1; i<=n; ++i)if(v[i])
for(etype *j=e[i]; j; j=j->next)
j->c-=d,j->pair->c+=d;
pi1 += d;
return true;
}

int main()
{
freopen("costflow.in","r",stdin);
freopen("costflow.out","w",stdout);
scanf("%d %d",&n,&m);
etype *Pe=new etype[m+m];
while(m--)
{
int s,t,c,u;
scanf("%d%d%d%d",&s,&t,&u,&c);
e[s]=new(Pe++)etype(t, c,u,e[s]);
e[t]=new(Pe++)etype(s,-c,0,e[t]);
e[s]->pair=e[t];
e[t]->pair=e[s];
}
do do memset(v,0,sizeof(v));
while(aug(1,maxint));
while(modlabel());
printf("%d\n",cost);
return 0;
}


这里使用的是连续最短路算法. 最短路算法? 为什么程序里没有 SPFA? Dijkstra? 且慢, 先让我们回顾一下图论中最短路算法中的距离标号. 定义

为点

的距离标号, 任何一个最短路算法保证, 算法结束时对任意指向顶点

、从顶点

出发的边满足

(条件1), 且对于每个

存在一个

使得等号成立 (条件2). 换句话说, 任何一个满足以上两个条件的算法都可以叫做最短路, 而不仅仅是 SPFA、Dijkstra, 算法结束后, 恰在最短路上的边满足

.

在最小费用流的计算中, 我们每次沿

的路径增广后都不会破坏条件 1, 但是可能破坏了条件 2. 不满足条件 2 的后果是什么呢? 使我们找不到每条边都满足

新的增广路. 只好每次增广后使用 Dijkstra, SPFA 等等算法重新计算新的满足条件 2 的距离标号. 这无疑是一种浪费. KM 算法中我们可以修改不断修改可行顶标, 不断扩大可行子图, 这里也同样, 我们可以在始终满足条件 1 的距离标号上不断修改, 直到可以继续增广 (满足条件 2).

  回顾一下 KM 算法修改顶标的方法. 根据最后一次寻找交错路不成功的 DFS, 找到

, 左边的点增加

, 右边的点减少

. 这里也一样, 根据最后一次寻找增广路不成功的 DFS, 找到 $ d = \min_{i \in V, j \notin V, u_{ij} > 0} \left\{ c_{ij} - D_i + D_j } \right\}$ , 所有访问过的点距离标号增加

. 可以证明, 这样不会破坏性质 1, 而且至少有一条新的边进入了

的子图.

  算法的步骤就是初始标号设为

, 不断增广, 如果不能增广, 修改标号继续增广, 直到彻底不能增广: 源点的标号已经被加到了

. 注意: 在程序中所有的 cost 均表示的是 reduced cost, 即

. 另外, 这个算法不能直接用于有任何负权边的图. 更不能用于负权圈的情况. 有关这两种情况的处理, 参见 (2.) 和 (3.) 中的说明.

  这样我们得到了一个简单的算法, 只需要增广, 改标号, 各自只有 7 行, 不需要 BFS, 队列, SPFA, 编程复杂度很低. 由于实际的增广都是沿最短路进行的, 所以理论时间复杂度与使用 SPFA 等等方法的连续最短路算法一致, 但节省了 SPFA 或者 Dijkstra 的运算时间. 实测发现这种算法常数很小, 速度较快, employee 这道题所有数据加在一起耗时都在 2s 之内.。

zkw的慢

实践中, 上面的这个算法非常奇怪. 在某一些图上, 算法速度非常快, 另一些图上却比纯 SPFA 增广的算法慢. 不少同学经过实测总结的结果是稠密图上比较快, 稀疏图上比较慢, 但也不尽然. 这里我从理论上分析一下, 究竟这个算法用于哪些图可以得到理想的效果.

  先分析算法的增广流程. 和 SPFA 直接算法相比, 由于同属于沿最短路增广的算法, 实际进行的增流操作并没有太多的区别, 每次的增流路径也大同小异. 因此不考虑多路增广时, 增广次数应该基本相同. 运行时间上主要的差异应当在于如何寻找增流路径的部分.

  那么 zkw 算法的优势在于哪里呢? 与 SPFA 相比, KM 的重标号方式明显在速度上占优, 每次只是一个对边的扫描操作而已. 而 SPFA 需要维护较为复杂的标号和队列操作, 同时为了修正标号, 需要不止一次地访问某些节点, 速度会慢不少. 另外, 在 zkw 算法中, 增广是多路进行的, 同时可能在一次重标号后进行多次增广. 这个特点可以在许多路径都费用相同的时候派上用场, 进一步减少了重标号的时间耗费.

  下面想一想 zkw 算法的劣势, 也就是 KM 重标号方式存在的问题. KM 重标号的主要问题就是, 不保证经过一次重标号之后能够存在增广路. 最差情况下, 一次只能在零权网络中增加一条边而已. 这时算法就会反复重标号, 反复尝试增广而次次不能增广, 陷入弄巧成拙的境地.

  接下来要说什么, 大家可能已经猜到了. 对于最终流量较大, 而费用取值范围不大的图, 或者是增广路径比较短的图 (如二分图), zkw 算法都会比较快. 原因是充分发挥优势. 比如流多说明可以同一费用反复增广, 费用窄说明不用改太多距离标号就会有新增广路, 增广路径短可以显著改善最坏情况, 因为即使每次就只增加一条边也可以很快凑成最短路. 如果恰恰相反, 流量不大, 费用不小, 增广路还较长, 就不适合 zkw 算法了.

本文部分材料来源于网络,若有不当之处,会及时更改。

文章原创,转载请标明出处,谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: