网络流(2)-----最小费用最大流(附带讲解SPFA算法)

一.最小费用最大流(简称费用流)概念

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.EK算法 + SPFA 最短路

(1)思路分析

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

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

<3>我们用每边单位流量的花费作为边权,假如一条增广路上每条边的花费分别为 c1,c2,.......ck , 那么这条边的最小流量为flow,则此增广路花费为 : c1 * flow + c2*flow + ..... + ck*flow = (c1+ c2 + c3 + .... + ck)*flow = Sum(ci)*flow

这里的Sum(ci)就是我们要求的最短路!

 

(2)SPFA算法求最短路(会的请略过。。QAQ)

<1>Dijkstra算法为什么无法求负权图

Dijkstra算法使用的是贪心的思想,每次在当前边中,选一条最短边并且标记该边最短距离已经确定。但是当存在负权边时,当前最短的边不一定就是最短距离,因为下一次拓展加上负权边时可能会变得更小,所以在负权图中,当前最短边不一定是到该点的最短距离,与Dijkstra的算法思想矛盾,因此Dijkstra不适用负权图。如下图(来自某博客。。忘记哪个了,博主看到轻戳我)

网络流(2)-----最小费用最大流(附带讲解SPFA算法)_第1张图片

<2>SPFA求最短路思路

摒弃了Dijkstra算法的选择当前最短边的思路,相反来想,既然存在负权边,那么最好的当然是用负权边去更新其他边,那么被负权边更新的其他边又可以用更小的距离去更新其他其他边,所以说由于负权边的存在,我们可以不断的用被更新的边去更新其他边,直到所有边都不再改变为止。(如果有负权无向边可能就成负权环了。。只能用于有向图??)

<3>SPFA算法代码:

#include 
#include
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 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

 

(3)费用流  EK最大流 + SPFA最短路:

用SPFA求增广路 + EK算法求最大流

#include 
#include
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 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

 

你可能感兴趣的:(-----图论-----,====数据结构学习====)