zoukankan      html  css  js  c++  java
  • 最小费用最大流

    转自  : https://www.cnblogs.com/gtarcoder/p/4890739.html

     

    最小费用最大流

        通过EK,Dinic,ISAP算法可以得到网络流图中的最大流,一个网络流图中最大流的流量max_flow是唯一的,但是达到最大流量max_flow时每条边上的流量分配f是不唯一的。 
        如果给网络流图中的每条边都设置一个费用cost,表示单位流量流经该边时会导致花费cost。那么在这些流量均为max_flow的流量分配f中,存在一个流量总花费最小的最大流方案。 
     min{sum(cost(i, j)*f(i,j) | (i, j)属于方案f中的边, f(i,j)为 边(i,j)上的流量, f为某一个最大流方案}。此即为最小费用最大流

    算法思想

        采用贪心的思想,每次找到一条从源点到达汇点的路径,增加流量,且该条路径满足使得增加的流量的花费最小,直到无法找到一条从源点到达汇点的路径,算法结束。 
        由于最大流量有限,每执行一次循环流量都会增加,因此该算法肯定会结束,且同时流量也必定会达到网络的最大流量;同时由于每次都是增加的最小的花费,即当前的最小花费是所有到达当前流量flow时的花费最小值,因此最后的总花费最小。

    求解步骤

    (1)找到一条从源点到达汇点的“距离最短”的路径,“距离”使用该路径上的边的单位费用之和来衡量。 
    (2)然后找出这条路径上的边的容量的最小值f,则当前最大流max_flow扩充f,同时当前最小费用min_cost扩充 f*min_dist(s,t)。 
    (3)将这条路径上的每条正向边的容量都减少f,每条反向边的容量都增加f。 
    (4)重复(1)--(3)直到无法找到从源点到达汇点的路径。

    需要注意几点: 
    1、注意超级源点和超级终点的建立。 
    2、初始化时,正向边的单位流量费用为cost[u][v],那么反向边的单位流量费用就为-cost[u][v]。因为回流费用减少。 
    3、费用cost数组和容量cap数组每次都要初始化为0。

        求解从源点到汇点的“最短”路径时,由于网络中存在负权边,因此使用SPFA来实现。

    #define INFINITE 1 << 26
    #define MAX_NODE 1005
    #define MAX_EDGE_NUM 40005
    struct Edge{
        int to;
        int vol;
        int cost;
        int next;
    };
    Edge gEdges[MAX_EDGE_NUM];
     
    int gHead[MAX_NODE];
    int gPre[MAX_NODE];
    int gPath[MAX_NODE];
    int gDist[MAX_NODE];
     
    int gEdgeCount;
    void InsertEdge(int u, int v, int vol, int cost){
        gEdges[gEdgeCount].to = v;
        gEdges[gEdgeCount].vol = vol;
        gEdges[gEdgeCount].cost = cost;
        gEdges[gEdgeCount].next = gHead[u];
        gHead[u] = gEdgeCount++;
     
        gEdges[gEdgeCount].to = u;
        gEdges[gEdgeCount].vol = 0;         //vol为0,表示开始时候,该边的反向不通
        gEdges[gEdgeCount].cost = -cost;    //cost 为正向边的cost相反数,这是为了
        gEdges[gEdgeCount].next = gHead[v];
        gHead[v] = gEdgeCount++;
    }
     
    //假设图中不存在负权和环,SPFA算法找到最短路径/从源点s到终点t所经过边的cost之和最小的路径
    bool Spfa(int s, int t){
        memset(gPre, -1, sizeof(gPre));
        memset(gDist, 0x7F, sizeof(gDist));
        gDist[s] = 0;
        queue<int> Q;
        Q.push(s);
        while (!Q.empty()){//由于不存在负权和环,因此一定会结束
            int u = Q.front();
            Q.pop();
     
            for (int e = gHead[u]; e != -1; e = gEdges[e].next){
                int v = gEdges[e].to;
                if (gEdges[e].vol > 0 && gDist[u] + gEdges[e].cost < gDist[v]){
                    gDist[v] = gDist[u] + gEdges[e].cost;
                    gPre[v] = u; //前一个点
                    gPath[v] = e;//该点连接的前一个边
                    Q.push(v);
                }
            }
        }
     
        if (gPre[t] == -1)  //若终点t没有设置pre,说明不存在到达终点t的路径
            return false;
        return true;
    }
     
    int MinCostFlow(int s, int t){
        int cost = 0;
        int flow = 0;
        while (Spfa(s, t)){
            int f = INFINITE;
            for (int u = t; u != s; u = gPre[u]){
                if (gEdges[gPath[u]].vol < f)
                    f = gEdges[gPath[u]].vol;
            }
            flow += f;
            cost += gDist[t] * f;
            for (int u = t; u != s; u = gPre[u]){
                gEdges[gPath[u]].vol -= f;   //正向边容量减少
                gEdges[gPath[u]^1].vol += f; //反向边容量增加
            }
        }
        return cost;
    }
  • 相关阅读:
    20150603_Andriod 多个窗体数据回调
    onActivityResult传值的使用
    20150602_Andriod 向窗体传递参数
    20150601_Andriod 打开新窗体
    C# 添加.DLL 出错的解决方法
    c# 中crystal report输出PDF文件
    参考_Android中,如何新建一个界面,并且实现从当前界面切换到到刚才新建的(另外一个)界面
    andriod 新建 Activity_ Form (详细设置)
    sql in
    如何取得GridView被隐藏列的值
  • 原文地址:https://www.cnblogs.com/fzw1523/p/10362142.html
Copyright © 2011-2022 走看看