最小费用最大流
最小费用最大流
通过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来实现。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | #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; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从零实现富文本编辑器#3-基于Delta的线性数据结构模型
· 记一次 .NET某旅行社酒店管理系统 卡死分析
· 长文讲解 MCP 和案例实战
· Hangfire Redis 实现秒级定时任务,使用 CQRS 实现动态执行代码
· Android编译时动态插入代码原理与实践
· 使用TypeScript开发微信小程序(云开发)-入门篇
· 没几个人需要了解的JDK知识,我却花了3天时间研究
· C#高性能开发之类型系统:从 C# 7.0 到 C# 14 的类型系统演进全景
· 管理100个小程序-很难吗
· 在SqlSugar的开发框架中增加对低代码EAV模型(实体-属性-值)的WebAPI实现支持