题目链接:http://poj.org/problem?id=2135
在最大流网络中,每条边只有一个限制条件,例如容量,带宽等,这是“最小性参数”,现在加上一个新的限制条件,例如费用,这是“可加性参数”。在两个限制条件的基础上就引出了最小费用最大流的问题:当流量为F时求费用最小的流;如果没有指定F,就是求最大流时的最小费用。一般的思路就是从零流开始,每次增加一个最小费用路径,经过多次增广,直到无法再增加路径,就得到了最大流,一般最短路径的寻找都是用SPFA,因为在构建成的网络中是有负边权的。SPFA+最大流=最小费用最大流。
关于最短路,其实最短路就是费用流的一种特例,单源最短路问题就是费用流将边的容量设置为1,cost设置为路径长度。在添加一个源点src,src到起点的变容量设置为1,cost设置为0,那么s到终点的最小费用最大流就是单源最短路径的解。这里设置边容量为1就是想让这条边只经过一次,事实上这是在最短路中本来就只能经过一次。
最小费用最大流中反向边的边权设置为0,但是他的费用是-cost,就相当于最大流中的反向边值一样,反向边就相当于将原来已经流过的流在返回回去,相当于这条支路没有流通过,但是费用边的反向边就相当于这个边的cost没有花费,所以边权值是负的,相当于将原来已经算入的cost再减去。
本题的题目环境是这样的,有一张无向图,起点是1,终点是n,每条边都是有边权的,问存不存在两条从起点到终点的道路,使得总路程是最小的,但是两条道路没有重合。其实就是一个确定流量的最小费用问题,因为每条边用过就不能再用,这和最大流的流量等于一个边的容量之后这条边就不能使用本质上是一样的,再结合边的权值,我们就知道这是一个最小费用确定流问题。只要建超级源和超级汇,并且流量为2就可以。注意超级源和起点之间的边的容量是2,花费是0,终点与超级汇之间的容量是2,花费是0。题目中保证流为2的时候一定存在两条路。
在我的代码中有一个地方要说明一下:一个是保存的方式,用领接表存边,并且在边的结构体中就设置了(u,v)的反向边在e[v]容器中的编号,这样在查找增广路上的流量的时候就很方便了,对于更新残余网络也是十分的简洁。
代码如下:
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<queue> 5 using namespace std; 6 const int maxn =1005; 7 #define inf 0x3f3f3f3f 8 int dis[maxn],pre[maxn],preve[maxn]; 9 int n,m; 10 struct edge{ 11 int to,cost,capacity,rev;//rev保存的是反向边中的第几条是他的前向边 12 edge(int t,int c,int cp,int re):to(t),cost(c),capacity(cp),rev(re){} 13 }; 14 vector<edge> e[maxn];//领接表存边,e[i]存第i个结点连接的所有的边 15 void addedge(int from,int to,int cost,int capacity)//一个有向边分成两个 16 { 17 e[from].push_back(edge(to,cost,capacity,e[to].size()));//保存的是下一个要加入的边 18 e[to].push_back(edge(from,-cost,0,e[from].size()-1));//反向边初始时刻跟最大流一样,流的大小为0 19 } 20 bool SPFA(int s,int t,int cnt)//基本就是SPFA板寻找一条最短增广路 21 { 22 bool inq[maxn];//spfa算法队列优化 23 memset(pre,-1,sizeof(pre)); 24 for(int i=1;i<=cnt;i++) 25 { 26 dis[i]=inf; 27 inq[i]=false; 28 } 29 dis[s]=0; 30 queue<int> q; 31 q.push(s); 32 while(!q.empty()) 33 { 34 int u=q.front(); 35 q.pop(); 36 inq[u]=0; 37 for(int i=0;i<e[u].size();i++) 38 { 39 if(e[u][i].capacity>0) 40 { 41 int v=e[u][i].to; 42 int cost=e[u][i].cost; 43 if(dis[u]+cost<dis[v]) 44 { 45 dis[v]=dis[u]+cost; 46 pre[v]=u;//保存v的前驱结点 47 preve[v]=i;//表示u的第i条边到达v,便于回溯 48 if(!inq[v]) 49 { 50 q.push(v); 51 inq[v]=1; 52 } 53 } 54 } 55 } 56 } 57 return dis[t]!=inf; 58 } 59 int mincost(int s,int t,int cnt)//cnt是传的点的数量 ,与最大流的更新过程基本完全相同 60 { 61 int cost=0; 62 while(SPFA(s,t,cnt))//SPFA找到一条最短路径 63 { 64 int v=t,flow=inf; 65 while(pre[v]!=-1)//寻找最大流 66 { 67 int u=pre[v],i=preve[v];//回溯找回之前的点和之前的边 68 flow=min(flow,e[u][i].capacity) ; 69 v=u;//回溯过程 70 } 71 v=t; 72 while(pre[v]!=-1)//更新残余网络 73 { 74 int u=pre[v],i=preve[v]; 75 e[u][i].capacity-=flow; 76 e[v][e[u][i].rev].capacity+=flow;//更新反向边 77 v=u;//回溯 78 } 79 cost+=dis[t]*flow;//费用累加,如果程序需要输出最大流,可以再累加flow 80 } 81 return cost; 82 } 83 int main() 84 { 85 while(scanf("%d%d",&n,&m)!=EOF) 86 { 87 for(int i=0;i<maxn-1;i++)e[i].clear(); 88 for(int i=1;i<=m;i++) 89 { 90 int u,v,w; 91 scanf("%d%d%d",&u,&v,&w); 92 addedge(u,v,w,1); 93 addedge(v,u,w,1);//两个无向边分成两条有向边存储,所以实际上将一条无向边分成了四条边来存储 94 } 95 int s=n+1,t=n+2;//设置超级源和超级汇 96 addedge(s,1,0,2); 97 addedge(n,t,0,2);//确定最大流量就是2,就是在确定流量条件下的最小费用问题 98 printf("%d ",mincost(s,t,n+2));//点数加上两个之后变成n+2 99 } 100 }