最小费用最大流(MCMF) 模板

Description


  在图问题中当最大流有多组解时,给每条边在附上一个单位费用的量,问在满足最大流时的最小费用是多少?

  

思路


  蒟蒻在这里套用一下博主STILLxjy的说法:

  给出一个容量网络,那他的最大流一定是一个定值(即使是有多个一样的最大值)。所以我们从开始的可行流开始增广时,最终的增广量是一定的。所以为了满足最小费用我们只需要每次找最小费用的增广路即可,直到流量为最大值。这个问题仅仅是在求增广路时先考虑费用最小的增广路,其他思想和EK思想一样。 
我们学过SPFA求最短路算法(bellman-ford的队列优化),所以我们将弧的费用看做是路径长度,即可转化为求最短路的问题了。只需要所走的最短路满足两个条件即可:1可增广cap> flow,2路径变短d[v]>d[u]+cost< u,v> 。

  模板如下:

  刘汝佳的紫书模板,容易TLE,不适用于点多边少的情况:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 210;
const int maxm = 220;

struct Edge {
    int from, to, cap, flow, cost;
    Edge(int u, int v, int c, int f, int w):from(u),to(v),cap(c),flow(f),cost(w)
    {}
};

struct MCMF {
    int n, m;
    vector<Edge> edges; //保存表
    vector<int> G[maxn]; ////保存邻接关系
    int inq[maxn]; //是否在队列中,用于SPFA算法
    int d[maxn]; //源点到i的最短路径估计值
    int p[maxn]; //记录路径,记录上一条弧
    int a[maxn]; //找到增广路径后的改进量

    MCMF() {}

    void init(int n) {
        this->n = n;
        for(int i = 0; i < n; i++) G[i].clear();
        edges.clear();
    }

    void addEdge(int from, int to, int cap, int cost) {
        edges.push_back(Edge(from, to, cap, 0, cost)); //正向
        edges.push_back(Edge(to, from, 0, 0, -cost)); //反向
        m = edges.size();
        G[from].push_back(m-2); //按照边的编号保存邻接关系
        G[to].push_back(m-1);
    }

    //队列优化的SPFA算法
    //寻找最小费用的增广路,使用引用同时修改原flow,cost
    bool bellmanFord(int s, int t, int& flow, long long& cost) {
        for(int i = 0; i < n; i++) d[i] = INF;
        memset(inq, 0, sizeof(inq));
        d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = INF;

        queue<int> Q;
        Q.push(s);
        while(!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = 0;
            for(int i = 0; i < G[u].size(); i++) {
                Edge& e = edges[G[u][i]];
                //寻找满足容量大于流量的可松弛边
                if(e.cap > e.flow && d[e.to] > d[u] + e.cost) {
                    d[e.to] = d[u] + e.cost;
                    p[e.to] = G[u][i];
                    a[e.to] = min(a[u], e.cap - e.flow);
                    //是否在队列中
                    if(!inq[e.to]) { Q.push(e.to); inq[e.to] = 1; }
                }
            }
        }
        if(d[t] == INF) return false; //如果d[t]没有被更新,相当于没找到增广路径,则没有最大流也没有最小费用
        flow += a[t]; //更新最大流
        cost += (long long)d[t] * (long long)a[t]; ;//单位流量乘以单位路径长度用来计算消耗
        //通过使用p[]保存的上一个边的值来对刚刚找到的增广路径上面的流量进行更新
        for(int u = t; u != s; u = edges[p[u]].from) {
        edges[p[u]].flow += a[t]; //正向边更新
        edges[p[u]^1].flow -= a[t];} //反向边更新(用位运算实现的)
        return true;
    }

    //需要保证初始网络中没有负权圈
    //计算从s到t的最小消耗cost,返回最大流
    int mincostMaxflow(int s, int t, long long& cost) {
        int flow = 0; cost = 0;
        while(bellmanFord(s, t, flow, cost));//不断寻找最短增广路径,直到找不到为止
        return flow;
    }
}g;

int main(void) {
    int nn, mm, s, t, u, v, cap, cost, totFlow = 0;
    long long totCost = 0;
    while(~scanf("%d%d", &nn, &mm)) {
        s = 1;
        t= nn;
        g.init(t+1);
        for (int i = 0; i < mm; i++) {
            scanf("%d%d%d%d", &u, &v, &cap, &cost);
            g.addEdge(u, v, cap, cost);
        }
        totFlow = g.mincostMaxflow(1, t, totCost);
        printf("最小花费:%lld 最大流:%d
", totCost, totFlow);
    }
    return 0;
}
View Code

  刘汝佳的白书模板,不用STL,很稳:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 210;
const int maxm = 220;

struct Edge {
    int from, to, cap, flow, cost;
    Edge () {} //作为Edge类数组的默认构造函数
    Edge (int u, int v, int ca, int fl, int co):from(u), to(v), cap(ca), flow(fl), cost(co)
    {}
};

struct MCFC {
    int n, m, s, t;
    Edge edges[maxm];
    int first[maxn];
    int next[maxm];
    int inq[maxn];
    int d[maxm];
    int p[maxn];
    int a[maxn];
    int Q[maxn];

    MCFC() {}

    void init(int n) {
        this->n = n;
        memset(first, -1, sizeof(first));
        m = 0;
    }

    void addEdge(int u, int v, int cap, int cost) {
        edges[m] = Edge(u, v, cap, 0, cost);
        next[m] = first[u];
        first[u] = m++;
        edges[m] = Edge(v, u, 0, 0, -cost);
        next[m] = first[v];
        first[v] = m++;
    }

    bool bellmanFord(int s, int t, int &flow, long long &cost) {

        for (int i = 0; i < n; i++) d[i] = INF;
        memset(inq, false, sizeof(inq));
        d[s] = 0; inq[s] = true; p[s] = 0; a[s] = INF;
        int front, rear;
        Q[rear = front = 0] = s;
        while (front <= rear) {
            int u = Q[front++];
            inq[u] = false;
            for (int i = first[u]; i != -1; i = next[i]) {
                Edge &e = edges[i];
                if (e.cap > e.flow && d[e.to] > d[u] + e.cost) {
                    d[e.to] = d[u] + e.cost;
                    p[e.to] = i;
                    a[e.to] = min(a[u], e.cap - e.flow);
                    if (!inq[e.to]) {Q[++rear] = e.to; inq[e.to] = true;}
                }
            }
        }
        if (d[t] == INF) return false;
        flow += a[t];
        cost += (long long)d[t] * (long long)a[t];
        int u = t;
        while (u != s) {
            edges[p[u]].flow += a[t];
            edges[p[u]^1].flow -= a[t];
            u = edges[p[u]].from;
        }
        return true;
    }

    int mincostMaxflow(int s, int t, long long& cost) {
        int flow = 0; cost = 0;
        while (bellmanFord(s, t, flow, cost));
        return flow;
    }
}g;

int main(void) {
    int nn, mm, s, t, u, v, cap, cost, totFlow = 0;
    long long totCost = 0;
    while(~scanf("%d%d", &nn, &mm)) {
        s = 1;
        t= nn;
        g.init(t+1);
        for (int i = 0; i < mm; i++) {
            scanf("%d%d%d%d", &u, &v, &cap, &cost);
            g.addEdge(u, v, cap, cost);
        }
        totFlow = g.mincostMaxflow(1, t, totCost);
        printf("最小花费:%lld 最大流:%d
", totCost, totFlow);
    }
    return 0;
}
View Code

  

————全心全意投入,拒绝画地为牢
原文地址:https://www.cnblogs.com/Bw98blogs/p/8818089.html