关于最小费用最大流的解题
- 格式:doc
- 大小:116.00 KB
- 文档页数:3
网络流:最小费用最大流(最简单的算法)最小费用流在OI 竞赛中应当算是比较偏门的内容,但是NOI2008 中employee 的突然出现确实让许多人包括zkw 自己措手不及。
可怜的zkw 当时想出了最小费用流模型,可是他从来没有实现过,所以不敢写,此题0 分。
zkw 现在对费用流的心得是:虽然理论上难,但是写一个能AC 题的费用流还算简单。
先贴一个我写的employee 程序:只有不到70 行,费用流比最大流还好写~程序代码:C++#include <cstdio>#include <cstring>using namespace std;const int maxint=~0U>>1;int n,m,pi[550]={0},cost=0;bool v[550]={0};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],*eb[550];int aug(int no,int m){if(no==n)return cost+=pi[1]*m,m;v[no]=true;for(etype *&i=e[no];i;i=i->next)if(i->u && !v[i->t] && pi[i->t]+i->c==pi[no])if(int d=aug(i->t,m<i->u?m:i->u))return i->u-=d,i->pair->u+=d,d;return 0;}bool modlabel(){int d=maxint,c;for(int i=1;i<=n;++i)if(v[i])for(etype *j=eb[i];j;j=j->next)if(j->u && !v[j->t])if((c=j->c-pi[i]+pi[j->t])<d)d=c;if(d==maxint)return false;for(int i=1;i<=n;++i)if(v[i])pi[i]+=d,e[i]=eb[i];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];}memmove(eb,e,sizeof(e));do do memset(v,0,sizeof(v));while(aug(1,maxint));while(modlabel());printf("%d\n",cost);return 0;}程序代码:CB大牛翻译的PASCALvarn,m,i,l,s,t,c,cost,u:longint;v:array[0..600]of boolean;dis:array[0..600]of longint;e_n,e_t,e_c,e_u,e_p,e_x:array[0..250000]of longint;function min(a,b:longint):longint;beginif a>b then exit(b);exit(a);end;procedure addedge(s,t,c,u,k:longint);begininc(l);e_n[l]:=e_n[s];e_n[s]:=l;//下一条边e_t[l]:=t;//边的另一端e_c[l]:=c;//边的费用e_u[l]:=u;//边的容量e_p[l]:=l+k;//对应的边end;procedure build(s,t,c,u:longint);beginaddedge(s,t,c,u,1);addedge(t,s,-c,0,-1);end;function aug(no,m:longint):longint;vari,d:longint;beginif no=n then begininc(cost,m*dis[1]);exit(m);end;v[no]:=true;i:=e_x[no];while i<>0 do beginif (e_u[i]>0)and(not v[e_t[i]])and(dis[e_t[i]]+e_c[i]=dis[no]) then begind:=aug(e_t[i],min(m,e_u[i]));if d>0 then begindec(e_u[i],d);inc(e_u[e_p[i]],d);e_x[no]:=i;exit(d);end;end;i:=e_n[i];end;e_x[no]:=i;exit(0);end;function modlabel:boolean;vard,i,j:longint;begind:=maxlongint;for i:=1 to n do if v[i] then beginj:=e_n[i];while j<>0 do beginif (e_u[j]>0)and(not v[e_t[j]])and(e_c[j]-dis[i]+dis[e_t[j]]<d) then d:=e_c[j]-dis[i]+dis[e_t[j]];j:=e_n[j];end;end;if d=maxlongint then exit(true);for i:=1 to n do if v[i] then beginv[i]:=false;inc(dis[i],d);end;exit(false);end;beginassign(input,'coflow.in');reset(input);assign(output,'coflow.out');rewrite(output);readln(n,m);l:=n;for m:=m downto 1 do beginreadln(s,t,u,c);build(s,t,c,u);end;repeatfor i:=1 to n do e_x[i]:=e_n[i];while aug(1,maxlongint)>0 do fillchar(v,sizeof(v),0);until modlabel;writeln(cost);close(output);end.这里使用的是连续最短路算法。
最小费用最大流问题例题讲解
最小费用最大流问题(Minimum Cost Maximum Flow Problem)是一种在特定的多媒体网络中传送给定体积的流量,使总花费最小化的一种算法。
它能满足一些实际生活中的求解,比如电力系统的供求、工厂的物料的分配和两地之间的物品的运输问题,以及更加复杂的产品开发和行业分工中的分布问题等等。
最小费用最大流问题的目标是在满足给定的最大流量要求的前提下,找出具有最小成本的流量方案。
这种问题的解决步骤如下:
1. 在图形中定义网络:用图形表示整个网络,每条边的容量是边上的流量上限。
2. 尝试找出最大流量:在不超过容量限制的前提下,找出输出流量最大的允许方案,也就是最小费用最大流量。
3. 计算最小成本:对所有边的成本进行总结,计算出最小成本。
下面以一个最小费用最大流问题的例题来说明:
假设有一个三角形的网络,它由一个源点S、一个汇点T、一个中间点O以及三条边组成,边的名字分别是SO、OT、OS,它们的容量分别是10、15和5,费用分别是5、3和2。
要求我们在此条件下求解最小费用最大流问题。
解:首先,我们可以求出最大流量:在边SO的容量为10时,我们可以将费用最小的边OT累加,得到最大流量值为10+3=13。
接下来,计算最小费用:根据上述算法,所有边的费用应该都大于等于0,才能累加而得到最大流量。
也就是说,最小费用为
5+3+2=10。
最后,最小费用最大流问题的解为:最大流量13,最小成本10。
图论专题小结:最小费用最大流算法一,给定流量F,求最小费用题意:网络中有两台计算机s,t。
现在每秒钟要从s到t传输大小为F的数据到t。
该网络中一共有N台计算机,其中有一些靠单向电缆相连接每条电缆用(from,to,cap,cost)表示从from发送给to,最大容量是cap,单位传输费用是cost。
问传输数据最小的花费是多少?解决最小费用流的一般思路是:每次都沿着最短路进行增广,增广一次之后累加本次增广的总费用,同时修改剩余的流量F,当F≤0时或dist[t]==INF时退出。
利用改进的Dijkstra算法求解(1)概述:题目要求在存在流量为F的前提下,总花费最少。
这类问题就是最小费用流问题。
该问题可以采用加入“势函数”后的Dijkstra算法解决。
因为对于每条边e=(u,v),有如下事实成立:h(v)≤h(u)+e.cost(其中h[u]表示s到u的最短距离)。
因此令dist[v]=dist[u]+e.cost+h[u]-h[v],。
那么所有的dist值必然大于等于0,这样就能用Dijkstra算法求解了。
下面代码中用了一个优先队列,每次优先出列dist值小的元素。
整个算法的时间复杂度是O(F*ElogV)(F是流量,E是边数,V是顶点数)。
1.#include<iostream>2.#include<algorithm>3.#include<string>4.#include<sstream>5.#include<set>6.#include<vector>7.#include<stack>8.#include<map>9.#include<queue>10.#include<deque>11.#include<cstdlib>12.#include<cstdio>13.#include<cstring>14.#include<cmath>15.#include<ctime>16.#include<functional>ing namespace std;18.19.#define N 100020.#define INF 10000000021.typedef pair<int, int>P;//first保存最短距离,second保存顶点的编号22.23.struct Edge24.{25.int to, cap, cost, rev;//终点,容量(指残量网络中的),费用,反向边编号26.Edge(int t, int c, int cc, int r) :to(t), cap(c), cost(cc), rev(r){}27.};28.int V;//顶点数29.vector<Edge>G[N];//图的邻接表30.int h[N];//顶点的势31.int dist[N];//最短距离32.int prevv[N];//最短路中的父结点33.int preve[N];//最短路中的父边34.35.void addedge(int from, int to, int cap, int cost)36.{37.G[from].push_back(Edge( to, cap, cost,G[to].size()));38.G[to].push_back(Edge( from, 0, -cost, G[from].size() - 1 ));39.}40.int min_cost_flow(int s, int t, int f)//返回最小费用41.{42.int res = 0;43.fill(h, h + V, 0);44.while (f>0)//f>0时还需要继续增广45.{46.priority_queue<P, vector<P>, greater<P> >q;47.fill(dist, dist + V, INF);//距离初始化为INF48.dist[s] = 0;49.q.push(P(0, s));50.while (!q.empty())51.{52.P p = q.top(); q.pop();53.int v = p.second;54.if (dist[v]<p.first)continue;//p.first是v入队列时候的值,dist[v]是目前的值,如果目前的更优,扔掉旧值55.for (int i = 0; i<G[v].size(); i++)56.{57.Edge&e = G[v][i];58.if (e.cap>0 && dist[e.to]>dist[v] + e.cost + h[v] - h[e.to])//松弛操作59.{60.dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];61.prevv[e.to] = v;//更新父结点62.preve[e.to] = i;//更新父边编号63.q.push(P(dist[e.to], e.to));64.}65.}66.}67.if (dist[t] == INF)//如果dist[t]还是初始时候的INF,那么说明s-t不连通,不能再增广了68.return -1;69.for (int j = 0; j<V; j++)//更新h70.h[j] += dist[j];71.int d = f;72.for (int x = t; x != s; x = prevv[x])73. d = min(d, G[prevv[x]][preve[x]].cap);//从t出发沿着最短路返回s找可改进量74. f -= d;75.res += d*h[t];//h[t]表示最短距离的同时,也代表了这条最短路上的费用之和,乘以流量d即可得到本次增广所需的费用76.for (int x = t; x != s; x = prevv[x])77.{78.Edge&e = G[prevv[x]][preve[x]];79. e.cap -= d;//修改残量值80.G[x][e.rev].cap += d;81.}82.}83.return res;84.}85.86.int main()87.{88.freopen("t.txt", "r", stdin);89.int m;90.while (cin >> V >> m)91.{92.for (int i = 0; i<m; i++)93.{94.int from, to, cap, cost;95.cin >> from >> to >> cap >> cost;96.addedge(from, to, cap, cost);97.}98.int s, t, f;99.cin >> s >> t >> f;100.cout << min_cost_flow(s, t, f) << endl;101.}102.return 0;103.}104.二,网络输出最大流时,求出最小的费用这就是最小费用最大流问题:既要求出最大流,又要求出达到最大流时候的最小费用。
利用lingo程序求最小费用最大流通常求最小费用最大流问题是分两个阶段:1,先求最大流。
2,再在最大流的基础上求最小费用流。
以下图为例。
其中如(5,8)=(容量,费用)。
求从S到T的最小费用最大流。
1,先求最大流,lingo程序为:MODEL:sets:nodes/s,1,2,3,t/;arcs(nodes,nodes)/s,1 s,2 1,t,1,3 2,1 2,3 3,t/:c,f;endsetsdata:c= 5 8 4 3 2 10 8;enddatamax = flow;@for(nodes(i)|i #ne# 1 #and# i #ne# @size(nodes):@sum(arcs(i,j):f(i,j))-@sum(arcs(j,i):f(j,i))=0);@sum(arcs(i,j)|i #eq# 1:f(i,j)) = flow;@for(arcs:@bnd(0,f,c));END结果是,最大流为12,2,再在最大流的基础上求最小费用流。
程序为:MODEL:sets:nodes/s,1,2,3,t/: ;arcs(nodes,nodes)/s,1 s,2 1,t,1,3 2,1 2,3 3,t/:b,c,f;endsetsdata:flow=12;b=8 7 9 2 5 9 4 ;c=5 8 4 3 2 10 8;enddatamin=@sum(arcs:b*f);@for(nodes(i)|i #ne# 1 #and# i #ne#@size(nodes):@sum(arcs(i,j):f(i,j))-@sum(arcs(j,i):f(j,i))=0);@sum(arcs(i,j)|i #eq# 1:f(i,j)) = flow;@for(arcs:@bnd(0,f,c));END结果是:Global optimal solution found.Objective value: 218.0000Infeasibilities: 0.000000Total solver iterations: 0Variable Value Reduced CostF( S, 1) 5.000000 -6.000000F( S, 2) 7.000000 0.000000F( 1, T) 4.000000 0.000000F( 1, 3) 3.000000 0.000000F( 2, 1) 2.000000 -2.000000F( 2, 3) 5.000000 0.000000 F( 3, T) 8.000000 -3.000000现利用lingo的子模型功能,将2个程序合二为一,可直接算出最小费用流。
关于最小费用最大流的解题
这是课本235页的一道习题
题目是:求如下图中网络的最小费用最大流。
弧旁数字(Cij,Rij)。
根据之前的例题14
解题过程如下:
(1)构造一个对偶网络,按狄克斯屈标号法找到最短路,得到相应的增广链,调整;
(2)再次构造一个对偶网络,如下图。
在书中曾提到在含负权的的网络中不能运用狄克斯屈标号法,只能用距离矩阵摹乘法。
可是距离矩阵摹乘法做起来确实是比较复杂,因此我在研究已经做过的几个题中发现,我们在计算最小费用最大流的时候,可以用如下方法来解该题。
解:
从S点出发,有三条路,分别到1和2,由于标号为-1的路是反向的,排除。
因此,我们选标号比较小的S—1这条路……依次类推,得到一条最短路:S—1—2
—4—3—T。
再按之前的解题思路解题。
再得到S—2—4—3—T;
不存在从S到T的最短路,故最大流为f(X*)=4+1=5,c(X′)=3×1+4×2+1×2+2×5+1×4+3×3+1×1=37;
(3)重复上面的动作;
(4)得到最小费用最大流。
我不喜欢复杂的做题的方法,因此,在做这个题的时候,由于之前提到过的狄克斯屈标号法不能用于含负权的网络图中,所以必须用到距离矩阵摹乘法,而距离矩阵摹乘法确实是很麻烦,又得画表,又得计算。
所以,我通过书上的图列推出这样来做这个题。
虽然并不一定这种做法是不是对的(我目前只在几个题目
中运用,结果是对的),但我相信这样一个方向是对的,以前的运筹学方法也是前辈们研究出来的。
另外,我还在维普中文网上看到了一篇题为《含负权最短路问题的一个改进标号法》的论文,载要:在不出现负回路的情况下,给出了在赋权的网络图中求两点之间的最短路问题的一个改进标号法,该方法对于网络图中出现负权的情况也有效。
最后给出了该算法的数值实验结果。
这篇文章中,提到了对狄克斯屈标号法的在负权网络不能运用的缺陷的改进。
这让我体会到了运筹学学习当中的另一个重要方面:我们不能拘泥于课本上一层不变的解题方法,应多了解运筹学发展的最新动态,掌握有关运筹学的最新知识。
通过对其的研究,从而找到更好的方法来解决相关的问题。
这是我在这次作业当中,所获得的心得体会。