网络流(二) 最小费用最大流问题

目录

一. 最小费用最大流(费用流)问题

1. 基本概念

2.引例

二. 求解算法

1. SPFA算法求最短路

1.1 算法背景

1.2 算法实现

2. 费用流算法

2.1 算法思路

2.2 算法实现


一. 最小费用最大流(费用流)问题

1. 基本概念

上篇文章我们讲解了最大流问题,那什么是最小费用最大流呢?听名字就可以看出,我们要在满足最大流的同时找到达成最大流的最小费用。对于一个网络流,最大流是一定的,但是组成最大流的费用是可以不同的,这里就有了在最大流网络上产生的费用流网络,就有了最小花费问题。

2.引例

问题描述(洛谷 3381)

如题,给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。

输入格式:

第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。

接下来M行每行包含四个正整数ui、vi、wi、fi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi),单位流量的费用为fi。

输出格式:

一行,包含两个整数,依次为最大流量和在最大流量情况下的最小费用。

示例:

4 5 4 3
4 2 30 2
4 3 20 3
2 3 20 1
2 1 30 9
1 3 40 5

                                      

50 280

二. 求解算法

1. SPFA算法求最短路

1.1 算法背景

        Dijkstra算法无法求负权图。因为Dijkstra算法使用的是贪心的思想,每次在当前边中,选一条最短边并且标记该边最短距离已经确定。但是当存在负权边时,当前最短的边不一定就是最短距离,因为下一次拓展加上负权边时可能会变得更小,所以在负权图中,当前最短边不一定是到该点的最短距离,与Dijkstra的算法思想矛盾,因此Dijkstra不适用负权图。如下图:

1.2 算法实现

        摒弃了Dijkstra算法的选择当前最短边的思路。相反来想,既然存在负权边,那么最好的当然是用负权边去更新其他边,那么被负权边更新的其他边又可以用更小的距离去更新其他其他边,所以说由于负权边的存在,我们可以不断的用被更新的边去更新其他边,直到所有边都不再改变为止。其代码如下:

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 100 +7;
struct Edge{
    int to,next,val;
}edge[maxn*100];
int head[maxn],n,m,tot,dist[maxn];
bool vis[maxn];
void addEdge(int a,int b,int c){
       edge[tot].to = b;edge[tot].next = head[a];edge[tot].val = c;head[a] = tot++;
}
void SPFA(int s){
     memset(vis,0,sizeof(vis));
     memset(dist,INF,sizeof(dist));
     queue<int> que;
     dist[s] = 0;
     que.push(s);
     while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = false;//取消当前点标记
        for(int i = head[u];~i;i = edge[i].next){
            int v = edge[i].to;
            if(dist[v] > dist[u] + edge[i].val){//如果连接点能更新
                dist[v] = dist[u] + edge[i].val;//更新
                if(!vis[v]){//如果未被标记,则标记入队,可以以更小的距离去更新其他边
                    vis[v] = true;
                    que.push(v);
                }
            }
        }
     }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF&&(n||m)){
        tot = 0;
        memset(head,-1,sizeof(head));
        for(int i = 0;i<m;i++){
            int a,b,v;
            scanf("%d%d%d",&a,&b,&v);
            addEdge(a,b,v);
        }
        SPFA(1);
        if(dist[n]==INF)printf("Nothing\n");
        else printf("%d\n",dist[n]);
    }
    return 0;
}

2. 费用流算法

2.1 算法思路

        只要是网络流的增广路思想,我们都要不断去寻找增广路。寻找增广路时我们可以使用DFS、BFS。但是增广路往往是曲折而漫长的,有时候我们可能走了错误的路径,因为我们的目的是到达汇点,但是中间有些路会使我们距离汇点更远,或者走到了不通的路,从而使得路径要重新寻找,所以我们在求最大流的时候使用了BFS来找增广路,距离汇点越来越近。

        在费用流中,我们仍要首先满足最大流的概念,那就必须仍要不断寻找增广路。但是为了让费用最小,那我们每次寻找的增广路都希望是花费最小的路。那么这里我们就可以使用最短路的思想来寻找增广路,每次优先使用花费最小的路径。因为图中反向建弧时,我们费用也要变成相反的(回流的时候相当于把钱拿回来),这就产生了负边问题,所以需要使用SPFA算法来求最短路。

2.2 算法实现

        我们用每边单位流量的花费作为边权,假如一条增广路上每条边的花费分别为 c1、c2.......ck ,那么这条边的最小流量为flow,则此增广路花费为:c1 * flow + c2*flow + ..... + ck*flow = (c1+ c2 + c3 + .... + ck) * flow = Sum(ci) * flow;这里的 Sum(ci) 就是我们要求的最短路!使用SPFA求增广路+EK算法求最大流的费用流算法如下:

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 5000 + 7;
struct Edge{
   int from,to,next,cap,flow,cost;
}edge[maxn*20];
int head[maxn],pre[maxn],dist[maxn],n,m,tot,s,t;
bool vis[maxn];
void addEdge(int a,int b,int c,int cost){//注意反向弧的负权值
     edge[tot].from = a;edge[tot].to = b;edge[tot].next = head[a];edge[tot].cap = c;edge[tot].flow = 0;edge[tot].cost = cost;head[a] = tot++;
     edge[tot].from = b;edge[tot].to = a;edge[tot].next = head[b];edge[tot].cap = 0;edge[tot].flow = 0;edge[tot].cost = -cost;head[b] = tot++;
}
bool SPFA(){//SPFA求增广路,dist[t]保存最小花费
    memset(dist,INF,sizeof(dist));
    memset(vis,0,sizeof(vis));
    queue<int> que;
    dist[s] = 0;
    vis[s] = 1;
    que.push(s);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = head[u];~i;i = edge[i].next){
            int v = edge[i].to;
            if(edge[i].cap > edge[i].flow && dist[v] > dist[u] + edge[i].cost){
                dist[v] = dist[u] + edge[i].cost;
                pre[v] = i;
                if(!vis[v]){
                    vis[v] = 1;
                    que.push(v);
                }
            }
        }
    }
    if(dist[t]!=INF)return true;
    return false;
}
int CostFlow(int &flow){//EK算法
    int mincost = 0;
    while(SPFA()){//能找到增广路
        int Min = INF;
        for(int i = t;i!=s;i = edge[pre[i]].from){//寻找最小流
            Min = min(Min,edge[pre[i]].cap - edge[pre[i]].flow);
        }
        for(int i = t;i!=s;i = edge[pre[i]].from){//处理所有边
            edge[pre[i]].flow+=Min;
            edge[pre[i]^1].flow-=Min;
        }
        flow+=Min;
        mincost+=(dist[t]*Min);//累和最小花费
    }
    return mincost;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        tot = 0;
        memset(head,-1,sizeof(head));
        scanf("%d%d%d%d",&n,&m,&s,&t);
        for(int i = 0;i<m;i++){
            int a,b,c,cost;
            scanf("%d%d%d%d",&a,&b,&c,&cost);
            addEdge(a,b,c,cost);
        }
        int MaxFlow = 0;
        int MinCost = CostFlow(MaxFlow);
        printf("%d %d\n",MaxFlow,MinCost);
    }
    return 0;
}

下面是最小费用最大流的示例代码(使用SPFA算法实现): ```python from collections import deque def spfa(graph, capacity, cost, source, sink): n = len(graph) flow = [0] * n dist = [float('inf')] * n in_queue = [False] * n parent_edge = [None] * n flow_value = 0 cost_value = 0 while True: # 使用BFS搜索增广路径 queue = deque([source]) dist[source] = 0 in_queue[source] = True while queue: u = queue.popleft() in_queue[u] = False for v, edge_id in graph[u]: if capacity[edge_id] > flow[edge_id] and dist[u] + cost[edge_id] < dist[v]: dist[v] = dist[u] + cost[edge_id] parent_edge[v] = edge_id if not in_queue[v]: queue.append(v) in_queue[v] = True # 如果找不到增广路径,算法结束 if parent_edge[sink] is None: break # 计算增广量 path_flow = float('inf') u = sink while u != source: edge_id = parent_edge[u] path_flow = min(path_flow, capacity[edge_id] - flow[edge_id]) u = graph[u][edge_id ^ 1][0] # 更新量和费用 flow_value += path_flow cost_value += path_flow * dist[sink] # 更新残量图 u = sink while u != source: edge_id = parent_edge[u] flow[edge_id] += path_flow flow[edge_id ^ 1] -= path_flow u = graph[u][edge_id ^ 1][0] return flow_value, cost_value # 示例用法 if __name__ == '__main__': # 有向图的邻接表表示 graph = [ [(1, 0), (2, 1)], # 节点0的出边 [(2, 2), (3, 3)], # 节点1的出边 [(3, 4), (4, 5)], # 节点2的出边 [(4, 6)], # 节点3的出边 [], # 节点4的出边 ] # 每条边的容量和费用 capacity = [3, 2, 2, 1, 4, 1, 2, 3] cost = [1, 2, 1, 3, 1, 4, 2, 3] # 源点和汇点 source = 0 sink = 4 # 计算最小费用最大流 flow, cost = spfa(graph, capacity, cost, source, sink) # 输出结果 print('最大量:', flow) print('最小费用:', cost) ``` 在上面的示例中,我们实现了一个名为`spfa`的函数,它接受一个有向图的邻接表表示、每条边的容量和费用、源点和汇点作为输入,返回最大量和最小费用。该函数使用SPFA算法来搜索增广路径,直到找不到增广路径为止。在每次找到增广路径后,我们计算增广量和费用,并更新残量图。最后,函数返回最大量和最小费用。 在示例中,我们使用一个五个节点的有向图来演示最小费用最大流的计算过程。该图的邻接表表示存储在名为`graph`的变量中。每条边的容量和费用存储在名为`capacity`和`cost`的变量中。源点和汇点分别是节点0和节点4。我们调用`spfa`函数来计算最小费用最大流,并打印结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿阿阿安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值