概念:
在同一个网络中,可能存在多个总流量相同的最大流,我们可以在计算流量的基础之上,给网络中的弧增加一个单位流量的费用(简称费用),在确保流量最大的前提下总费用最小——最小费用最大流。
算法思路:
边权的数据有两个,一个是容量,一个是费用。
主要是两部分,①spfa来求边权(根据费用w)最短路;②在最短路基础上统计计算最小费用的。用spfa求出最短路(其中有个pre[i]数组表示指向i点的边序号),然后再倒序从终点沿着最短路走反向边,找到该最短路上的最小边权——流量。然后再来走一遍,这次需要每次都把最小费用统计一下(+=最小流量*费用),同时从当前边权容量上减去当前流量,以便进入下一次循环的spfa。
const int MAX_N = 1000; const int MAX_M = 10000; const int inf = 0x3f3f3f3f; struct edge { int v, c, w, next; // v 表示边的另一个顶点,c 表示当前剩余容量,w 表示单位流量费用 } e[MAX_M]; int p[MAX_N], s, t, eid; // s 表示源点,t 表示汇点,需要在进行 costflow 之前设置完毕 void init() { memset(p, -1, sizeof(p)); eid = 0; } void insert(int u, int v, int c, int w) { e[eid].v = v; e[eid].c = c; e[eid].w = w; e[eid].next = p[u]; p[u] = eid++; } void addedge(int u, int v, int c, int w) { insert(u, v, c, w); insert(v, u, 0, -w); } bool inq[MAX_N]; int d[MAX_N]; // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路 int pre[MAX_N]; // 最短路中连向当前顶点的边的编号 bool spfa() { // 以源点 s 为起点计算单源最短路,如果不存在从 s 到 t 的路径则返回 false,否则返回 true memset(inq, 0, sizeof(inq)); memset(d, 0x3f, sizeof(d)); memset(pre, -1, sizeof(pre)); d[s] = 0; inq[s] = true; queue<int> q; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); inq[u] = false; for (int i = p[u]; i != -1; i = e[i].next) { if (e[i].c) { //注意这个条件!!!spfa这里是以w求最短路的,但仍然不能忽略容量c的考虑! int v = e[i].v; if (d[u] + e[i].w < d[v]) { d[v] = d[u] + e[i].w; pre[v] = i; if (!inq[v]) { q.push(v); inq[v] = true; } } } } } return pre[t] != -1; } int costflow() { // 计算最小费用最大流 int ret = 0; // 累加和 while(spfa()) { int flow = inf; for(int i = t; i != s; i = e[pre[i]^1].v) { flow = min(e[pre[i]].c, flow); // 计算当前增广路上的最小流量 } for(int i = t; i != s; i = e[pre[i]^1].v) { e[pre[i]].c -= flow; //容量是一定要跟着变化的,毕竟要继续循环使用spfa来更新下一条“能走的(看容量)”最短路。 e[pre[i]^1].c += flow; ret += e[pre[i]].w * flow; } } return ret; }