【Luogu】P3381最小费用最大流模板(SPFA找增广路)

  题目链接

  哈  学会最小费用最大流啦

  思路是这样。

  首先我们有一个贪心策略。如果我们每次找到单位费用和最短的一条增广路,那么显然我们可以把这条路添加到已有的流量里去——不管这条路的流量是多大,反正它能扩大现有流量,而且目前为止它是可以扩大流量的所有路径中单位花费最少的。

  然后我们就把这条路填上。想想看当我们找不到这样一条路的时候会发生什么?

  那就是没有增广路了。恭喜我们获得最小费用最大流。这是为什么呢?

  首先没有任何一条增广路的时候我们肯定获得最大流没错

  然后我们回顾我们扩展的方式。每次我们都选择了一条单位费用和最短的路径,也就是说,我们总是先把便宜的路尽可能利用完了,才去利用那些贵的。鉴于此,我们总是已经把更便宜的填满了,整张图上没有更便宜的路了,才去尝试探索更贵的。

  那么这就是最小费用qwq

  那这个思路怎么实现呢?

  我们可以把边的长度设为单位费用,然后对每个点SPFA。这样SPFA算出来的每个点的距离就是可走增广路中从起点到这个点最少的单位费用和。

  然后SPFA过程中顺便求路径上的流量。顺便记录用了哪些边  最后暴力回跳修改边的信息。

  

#include<cstdio>
#include<cstring>
#include<cctype>
#include<cstdlib>
#include<algorithm>
#include<queue>
#define maxn 5050
#define maxm 10050
using namespace std;
inline long long read(){
    long long num=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')    f=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        num=num*10+ch-'0';
        ch=getchar();
    }
    return num*f;
}

inline int count(int i){    return i&1?i+1:i-1;    }

struct Edge{
    int from,to,next,val,dis,flow;
}edge[maxm*2];
int head[maxn],num;
inline void addedge(int from,int to,int val,int dis){
    edge[++num]=(Edge){from,to,head[from],val,dis,0};
    head[from]=num;
}
inline void add(int from,int to,int val,int dis){
    addedge(from,to,val,dis);
    addedge(to,from,0,-dis);
}

bool vis[maxn];
int pre[maxn];
int cost[maxn];
int dst[maxn];

struct Answer{
    bool flag;long long flow;long long cost;
};

Answer spfa(int Start,int End){
    Answer ans=(Answer){0,0,0};
    memset(dst,127/3,sizeof(dst));    dst[Start]=0;    cost[Start]=0x7fffffff;
    queue<int> f;
    f.push(Start);
    while(!f.empty()){
        int from=f.front();    f.pop();
        vis[from]=0;
        for(int i=head[from];i;i=edge[i].next){
            int to=edge[i].to;
            if(edge[i].val<=edge[i].flow||dst[to]<=dst[from]+edge[i].dis)    continue;
            dst[to]=dst[from]+edge[i].dis;
            pre[to]=i;
            cost[to]=min(cost[from],edge[i].val-edge[i].flow);
            if(!vis[to]){
                vis[to]=1;
                f.push(to);
            }
        }
    }
    if(dst[End]==dst[0])    return ans;
    ans.flag=1;ans.flow+=cost[End];    ans.cost+=(long long)dst[End]*cost[End];
    int now=End;
    while(now!=Start){
        int ret=pre[now];
        edge[ret].flow+=cost[End];
        edge[count(ret)].flow-=cost[End];
        now=edge[ret].from;
    }
    return ans;
}

long long Flow,Cost;

int main(){
    int n=read(),m=read(),Start=read(),End=read();
    for(int i=1;i<=m;++i){
        int from=read(),to=read(),val=read(),dis=read();
        add(from,to,val,dis);
    }
    while(1){
        Answer ans=spfa(Start,End);
        if(!ans.flag)    break;
        if(!ans.flow)    break;
        Flow+=ans.flow;
        Cost+=ans.cost;
    }
    printf("%lld %lld",Flow,Cost);
    return 0;
}

 

转载于:https://www.cnblogs.com/cellular-automaton/p/8177744.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值