图论-网络流(最小费用最大流)的探究

对应OJ题目 洛谷模板题P3381

问题分析

  1. 首先应该弄清楚什么是最大网络流
  2. 在生产生活中,除了要考虑最大流之外,还会考虑实现最大流的代价(耗费),因为不同的规格(容量…)的管道的成本是不同的
  3. 现在我们想象假如我们有一个流量网络,现在每个边除了流量,现在还有一个单位费用,这条边的费用相当于它的单位费用乘上它的流量,我们要保持最大流的同时,还要保持边权最小,这就是最小费用最大流问题。因为在一个网络流图中,最大流量只有一个,但是“流法”有很多种,每种不同的流法所经过的边不同因此费用也就不同。

思路分析

  1. 在解决最大流问题时,我们通过不断寻找增广路来扩充增广量,从而让总流量达到最大。显然,对于同一个网络,最大流只有唯一一个值,但是流法会有很多种。这就意味着增广路的确定顺序并不会影响最大流,那么我们可以贪心地按照代价从小到大的顺序来寻找增广路。
  2. 对于每条增广路, c o s t = L s − > t   ∗   f l o w L cost = L_{s->t} \ * \ flow_L cost=Ls>t  flowL我不会证明,但是 最小代价增广路 <=> 最小单位代价增广路
  3. 对于一条边,只有残余容量flow>0才认为可以走;对于一条边,正向走代价cost>0,则反向走代价理应为负数(正向边的相反数)。
  4. 在寻找最短路径的过程中,应记录下最短路径和这条路径的真实流量
  5. 由于存在负权边,所以无法直接利用Dijkstra算法来找最短路径,此处我们使用可以处理负权边的SPFA算法
  6. 注意手写队列时应实现为循环队列,以免溢出。
  7. 注意链式前向星的索引idx应从2开始,这样可以方便地访问到正向边对应的反向边。因为正向边和对应的反向边总是挨着的,利用xor(^)可以方便转换。

实现代码(EK+SPFA)

#include 
#include 
#include 

using namespace std;
const int N=5e3+10,M=5e4+10,INF=0x3f3f3f3f;

int n,m,s,t;
int idx=2,first[N],nxt[M*2],to[M*2],flow[M*2],cost[M*2];
int q[N],pre[N],dist[N],in[N];//pre[]用于追溯路径,dist[]用于记录最短距离,in[]用于记录层层限制后的输入流量
bool inq[N];//q[]、inq用于spfa算法
int front=0,back=0,maxflow=0,mincost=0;

inline void addE(int u,int v,int f,int c)
{
    to[idx]=v,flow[idx]=f,cost[idx]=c;
    nxt[idx]=first[u],first[u]=idx++;
}

bool spfa()
{
    memset(pre,0,sizeof(pre));
    memset(dist,INF,sizeof(dist));//使用0x3f3f3f3f可以快速初始化,避免循环初始化
    back=front=0;
    q[(back++)%N]=s,inq[s]=true,dist[s]=0,in[s]=INF;//源点s距最短距离为0,输入流量为INF
    while(back!=front)
    {
        int u=q[(front++)%N];inq[u]=false;
        for(int p=first[u];p;p=nxt[p])
        {
            int v=to[p],f=flow[p],c=cost[p];
            if(f && dist[v]>dist[u]+c)//有流量(可以走),且可以缩短距离
            {
                dist[v]=dist[u]+c;//更新距离
                in[v]=min(in[u],f);//更新流量
                pre[v]=p;//记录路径(记录前向星索引更方便)
                if(!inq[v])
                    q[(back++)%N]=v,inq[v]=true;
            }
        }
    }
    return dist[t]!=INF;//距离为INF表明无法到达,即没有更多的增广路了
}

void update()
{
    int x=t,f=in[t];
    while(x!=s)
    {
        int p=pre[x];
        flow[p]-=f;
        flow[p^1]+=f;
        x=to[p^1];//寻找前一个点
    }
    maxflow+=f,mincost+=f*dist[t];//累计答案
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=0;i<m;i++)
    {
        int u,v,f,c;
        scanf("%d%d%d%d",&u,&v,&f,&c);
        addE(u,v,f,c);
        addE(v,u,0,-c);//反向边残余容量f=0,单位费用c=-c
    }
    while(spfa())
        update();
    printf("%d %d\n",maxflow,mincost);
    return 0;
}

参考资料

  • https://12349.blog.luogu.org/solution-p3381 (代码合我习惯)

你可能感兴趣的:(算法学习)