目录
一. 最小费用最大流(费用流)问题
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;
}