luogu3381【模板】最小费用最大流(zkw费用流板子)

%%%xtx的代码了好久,又翻了很多文章,终于算是差不多看懂了???
费用流问题,是建立在最大流问题基础之上的,也就是说,现在流要钱了,我们要求出最大流,可能有很多个最大流,我们还要费用最小的那个。比较直观的一个想法便是:把原来的bfs分层变成spfa等最短路算法,求出从s到t费用最小的一条路径来增广。然而这样我们就失去了Dinic的一个优势,即多路增广。因为每次我们进行增广后,最短路都不一定再满足了,我们需要重新运行spfa来算。这样就比较慢。zkw费用流改进在哪里呢?他使用了类似km算法的每次修改顶标的做法。

任何一个最短路算法保证, 算法结束时对任意指向顶点 i 、从顶点 j 出发的边满足 DiDj+cij (条件1), 且对于每个 i 存在一个 j 使得等号成立 (条件2). 换句话说, 任何一个满足以上两个条件的算法都可以叫做最短路, 而不仅仅是 SPFA、Dijkstra, 算法结束后, 恰在最短路上的边满足 Di=Dj+cij .

在最小费用流的计算中, 我们每次沿 Di=Dj+cij 的路径增广后都不会破坏条件 1, 但是可能破坏了条件 2. 不满足条件 2 的后果是什么呢? 使我们找不到每条边都满足 Di=Dj+cij 新的增广路. 只好每次增广后使用 Dijkstra, SPFA 等等算法重新计算新的满足条件 2 的距离标号. 这无疑是一种浪费. KM 算法中我们可以通过不断修改可行顶标, 不断扩大可行子图, 这里也同样, 我们可以在始终满足条件 1 的距离标号上不断修改, 直到可以继续增广 (满足条件 2).

Di+cij≥Dj ⇔ Di−Dj+cij≥0 ①
Di+cij=Dj ⇔ Di−Dj+cij=0 ②
对于一个顶标D,我们可以不断的dfs找Di−Dj+cij=0的增广路经
假设我们当前dfs失败
即使失败还是有一些点能满足Di−Dj+cij=0的
这些点被我们当前dfs到了
我们记这些点的点集为 V
找到Δ=min{DiDj+cij}|iV,jV,flowij>0
然后我们对 iV,Dπi=DiΔ

证明:
弧(i,j)可以分成四类,再根据当前dfs失败的条件,有:

i∈V,j∉V 原来Di−Dj+cij≥Δ>0 新图 DπiDj+cij0

i∈V,j∈V 原来Di−Dj+cij=0 新图 DπiDπj+cij=0

i∉V,j∉V 原来Di−Dj+cij≥0 新图 DiDj+cij0

i∉V,j∈V 原来Di−Dj+cij≥0 新图 DiDπj+cij>=Δ

可以发现第一类弧中一定有至少一条满足
原来Di−Dj+cij=Δ 新图 DπiDj+cij=0
即至少有一条新的边进入了 Dj=Di+cij 的子图。

这样直到找不到这样的 Δ ,就不会再有增广路了。
本来我们要记录D数组,每次修改距离标记改的是D数组,但是我们也可以不计这个D数组,每条边(u,v)的c就表示 DuDv+cuv ,则我们每次减正向边,加反向边,也是一样的效果。那么在最短路径上的边,也就是c为0的边。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 5010
#define inf 0x3f3f3f3f
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,m,s,t,h[N],num=1,cost=0,price=0,mxflow=0;
bool vis[N];
struct edge{
    int to,next,w,c;
}data[50010<<1];
inline void add(int x,int y,int w,int c){
    data[++num].to=y;data[num].next=h[x];h[x]=num;data[num].w=w;data[num].c=c;
    data[++num].to=x;data[num].next=h[y];h[y]=num;data[num].w=0;data[num].c=-c;
}
inline int dinic(int x,int low){
    vis[x]=1;if(x==t){cost+=low*price;mxflow+=low;return low;}int tmp=low;
    for(int i=h[x];i;i=data[i].next){
        int y=data[i].to;if(vis[y]||!data[i].w||data[i].c) continue;
        int res=dinic(y,min(tmp,data[i].w));
        tmp-=res;data[i].w-=res;data[i^1].w+=res;
        if(!tmp) return low;
    }return low-tmp;
}
inline bool label(){
    int d=inf;
    for(int x=1;x<=n;++x){
        if(!vis[x]) continue;
        for(int i=h[x];i;i=data[i].next){
            int y=data[i].to;if(vis[y]) continue;
            if(data[i].w&&data[i].c<d) d=data[i].c;
        }
    }if(d==inf) return 0;
    for(int x=1;x<=n;++x){
        if(!vis[x]) continue;
        for(int i=h[x];i;i=data[i].next)
            data[i].c-=d,data[i^1].c+=d;
    }price+=d;return 1;
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();s=read();t=read();
    while(m--){
        int x=read(),y=read(),w=read(),c=read();add(x,y,w,c);
    }do 
        do memset(vis,0,sizeof(vis));while(dinic(s,inf));
    while(label());
    printf("%d %d\n",mxflow,cost);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值