专栏文章

题解:P3381 【模板】最小费用最大流

P3381题解参与者 3已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@mioxb645
此快照首次捕获于
2025/12/03 02:40
3 个月前
此快照最后确认于
2025/12/03 02:40
3 个月前
查看原文

这是一道模板题。在做这道题之前,你应该要学会:

对于网络 (V,E)(V, E),边 (u,v)(u, v) 带容量 f(u,v)f(u, v) 和费用 c(u,v)c(u,v)。求出 STS \to T 的所有最大流中所选边费用最小的方案,称作网络的最小费用最大流(MCMF)。
对此,我们以求解最大流的 Edmonds-Karp 算法为基础。EK 的过程是,每次选择一条 STS \to T非满流路径并将其流满,称作一次增广,增广的路径称作增广路。
在求解最小费用的过程中,我们以 c(u,v)c(u, v) 为边权,每次选择 STS \to T最短非满流简单路径作为增广路,这样就能求出最大流当中的最小费用。这就是 EK 求最大流的方法。

我们想想这个东西为什么是对的。
EK 结束的条件是图中不存在合法路径。一方面,根据最大流最小割定理,一个流是最大流当且仅当途中不存在增广路径。所以求出最大流的正确性显然。
另一方面,我们需要说明这种方案所需要的费用是最少的。记处于一个局面时的最小费用为 C0C_0,我们通过最短路进行一次增广后的最小费用为 C1C_1,此时假设我们存在另外一条增广路径求出的费用为 C2C_2C2<C1C_2 < C_1。由于 C1C_1 时当前局面下的的最短简单路径,因此 C2C_2 的增广路必然存在负环,和增广路的简单性相违背。
综上所述,我们证明了该算法的正确性。

我们回到实现上。我们可以使用 SPFA 算法寻找最短简单路径,在松弛的时候记录每个点的前驱。随后我们沿着记录的路径进行增广,同时将最小费用加上路径的边权和。
由于一次增广至少使最大流增加 11,假设原图最大流为 ff,因此最多经过 ff 轮增广后得出答案。由于使用的是 SPFA,因此一次增广的复杂度为 O(nm)O(nm),总复杂度是 O(nmf)O(nmf)
CPP
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define N 5006
#define M 50006
using namespace std;
int n,m;
struct MCMF_Graph{//by dyc2022
int head[N],tot=2,s,t;
int dis[N],incf[N],pre[N],vis[N],maxflow,mincost;
struct Node{int nxt,to,w,c;}E[M<<1];
void addedge(int u,int v,int w,int c){E[tot]={head[u],v,w,c},head[u]=tot++;}
void addflow(int u,int v,int w,int c){addedge(u,v,w,c),addedge(v,u,0,-c);}
int SPFA()
{
    for(int i=1;i<=n;i++)dis[i]=1e15;
    queue<int> q;
    q.push(s),dis[s]=0,incf[s]=1e15,incf[t]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=E[i].nxt)
        {
            int v=E[i].to,w=E[i].w,c=E[i].c;
            if(!w||dis[v]<=dis[u]+c)continue;
            dis[v]=dis[u]+c,incf[v]=min(incf[u],w),pre[v]=i;
            if(!vis[v])q.push(v),vis[v]=1;
        }
    }
    return incf[t];
}
void EK()
{
    maxflow+=incf[t];
    for(int u=t;u!=s;u=E[pre[u]^1].to)
    {
        E[pre[u]].w-=incf[t],E[pre[u]^1].w+=incf[t];
        mincost+=incf[t]*E[pre[u]].c;
    }
}
void getflow(){while(SPFA())EK();}}G;
main()
{
    scanf("%lld%lld%lld%lld",&n,&m,&G.s,&G.t);
    for(int i=1,u,v,w,c;i<=m;i++)
        scanf("%lld%lld%lld%lld",&u,&v,&w,&c),G.addflow(u,v,w,c);
    G.getflow();
    printf("%lld %lld\n",G.maxflow,G.mincost);
    return 0;
}

评论

2 条评论,欢迎与作者交流。

正在加载评论...