网络流之最小费用流

前言:在最大流问题的网络中,给边新加上费用,求流量为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;
}

时间: 2024-12-28 17:55:04

网络流之最小费用流的相关文章

HDU 3488Tour(网络流之最小费用流)

题目地址:hdu3488 这题跟上题基本差不多啊....详情请戳这里. 另外我觉得有要改变下代码风格了..终于知道了为什么大牛们的代码的变量名都命名的那么长..我决定还是把源点与汇点改成source和sink吧..用s和t太容易冲突了...于是如此简单的一道题调试到了现在..sad... 代码如下: #include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #

HDU 1853Cyclic Tour(网络流之最小费用流)

题目地址:HDU1853 费用流果然好神奇..还可以用来判断环...如果每个点都是环的一部分而且每个点只能用到一次的话,那每个点的初度入度都是1,这就可以利用网络流来解决,只要拆点令其流量为1,就限制了每个点只能用一次,每次左边的连到右边的,就相当于左边点的一次初度和右边的点的一次入度,很容易想象出来.最后只要判断总流量是否为n即可,因为如果总流量为n的话,说明每个点都出了一次度,每个点都入了一次度,而且由于拆点的流量限制,充分说明了每个点的初度入度都是1.进而说明了每个点都在环里.然后输出最后

POJ 2195Going Home(网络流之最小费用流)

题目地址:POJ2195 本人职业生涯费用流第一发!!快邀请赛了,决定还是多学点东西,起码碰到简单的网络流要A掉.以后最大流费用流最小割就一块刷. 以前费用流在我心目中一直是高大上,高不可攀的形象,可这两天才发现,原来费用流就是一个spfa再加点东西...一直以为费用流会比最大流的isap要麻烦好多,毕竟多了一个费用的元素....我真的错了..仔细研究了一下,只用一个spfa确实就可以解决了... 这题是一个入门题(虽然还多了些处理..),建图思路很简单,就是将人与源点相连,流量为1,费用为0,

HDU 3435A new Graph Game(网络流之最小费用流)

题目地址:HDU 3435 这题刚上来一看,感觉毫无头绪. .再细致想想.. 发现跟我做的前两道费用流的题是差点儿相同的. 能够往那上面转换. 建图基本差点儿相同.仅仅只是这里是无向图.建图依旧是拆点,推断入度出度.最后推断是否满流,满流的话这时的费用流是符合要求的.输出.不能满流的话,输出NO. 代码例如以下: #include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.

算法学习三阶段

?? 第一阶段:练经典经常使用算法,以下的每一个算法给我打上十到二十遍,同一时候自己精简代码, 由于太经常使用,所以要练到写时不用想,10-15分钟内打完,甚至关掉显示器都能够把程序打 出来. 1.最短路(Floyd.Dijstra,BellmanFord) 2.最小生成树(先写个prim,kruscal 要用并查集,不好写) 3.大数(高精度)加减乘除 4.二分查找. (代码可在五行以内) 5.叉乘.判线段相交.然后写个凸包. 6.BFS.DFS,同一时候熟练hash 表(要熟,要灵活,代码要

我的ACM新的开始

大二下了,老大不小了,现在要开始正式认认真真地做点事了!!! 现在让我重新开始,先从基础开始打牢了再说. 一下是断断续续从网上摘录下来的东西,先好好对着完成一遍再说 正在学(learning),未学(waiting),已学(cut  vovering) • 第一阶段 1.最短路(Floyd.Dijstra,BellmanFord) 2.最小生成树(先写个prim,kruscal要用并查集,不好写) 3.大数(高精度)加减乘除 4.二分查找. (代码可在五行以内) 5.叉乘.判线段相交.然后写个凸

学ACM的必备基础算法

不知是哪位大牛写的,迷茫的参考一下吧! 参加ACM比赛一般要做到50行以内的程序不用调试.100行以内的二分钟内调试成功.acm主要是考算法的,主要时间是花在思考算法上,不是花在写程序与debug上. 第一阶段: 练经典常用算法,下面的每个算法给我打上十到二十遍,同时自己精简代码,因为太常用,所以要练到写时不用想,10-15分钟内打完,甚至关掉显示器都可以把程序打 出来. 1.最短路(Floyd, Dijstra,BellmanFord) 2.最小生成树(先写个prim, kruscal要用并查

高手给的训练计划

高手给的训练计划 一般要做到50行以内的程序不用调试.100行以内的二分钟内调试成功.acm主要是考算法的 ,主要时间是花在思考算法上,不是花在写程序与debug上. 下面给个计划你练练: 第一阶段:练经典常用算法,下面的每个算法给我打上十到二十遍,同时自己精简代码, 因为太常用,所以要练到写时不用想,10-15分钟内打完,甚至关掉显示器都可以把程序打 出来. 1.最短路(Floyd.Dijstra,BellmanFord) 2.最小生成树(先写个prim,kruscal要用并查集,不好写) 3

要开始算法了 什么顺序呢?

ACM 中常用的算法有哪些? 在网上看到别人ACM学习的心得,转载过来,源地址不记得了,当时是百度的.内容如下: 网络上流传的答案有很多,估计提问者也曾经去网上搜过.所以根据自己微薄的经验提点看法. 我ACM初期是训练编码能力,以水题为主(就是没有任何算法,自己靠动脑筋能够实现的),这种题目特点是麻烦,但是不难,30-50道题目就可以了. 然后可以接触一下基础的算法,我感觉搜索方向的比较不错,可以解决很多问题,深搜,广搜,然后各种剪枝能力的锻炼. 搜索感觉不错了就可以去看看贪心,图论,和动态规划