算法学习:费用流

【前置知识】

最大流

 


【问题描述】

给定一个图,图上的边每条边有流量,流量有费用,

求在流量为 F 时费用的最小值

 

 


【分析思路】

求费用最小,我们很容易想到求最短路径,我们只需要将费用看作代价然后求最短路不久能求出来最小费用了嘛

但是,问题来了

我们又如何能够在求最小费用的同时,满足流量最终为F的条件

在最大流中提到,每次增广都是在残余网络中通过询问 s 至 t 的残余网络进行增广

所以,我们每次在残余网络上求最短路就可以了

这里注意,残余网络中的反向边的费用应该时原边费用的相反数

这样在计算的时候,才能保证过程是可逆的

【证明】

在残余网络上求最短路是最小费用

证:

1  设有  f 为以以上方式求出的结果, 设  f ’ 和 f 流量相同,但是费用更少

因为  f ‘ - f 的流量流入流出为 0,所以他是成环的

又因为 f ’ - f 的费用是负的,所以在这些环中,存在负环

结论:f 是最小费用流   <====>  残余网络中没有负环

(因为负环显而易见的能够通过转圈减少费用)

2  对于流量为 0 的流 f0 ,其残余网络为原图,原图没有负环,则f0 就是最小费用流

设流量为i 的流 fi 是最小流,并且下一步我们求得流量为 i + 1的流 f[i+1],按照方法,f[ i + 1 ] - f [ i ]就是 f[ i ]对应的参余网络中 s 到 t 的最短路

假设 f [ i + 1 ] ' 的费用比 f [ i + 1 ]还小,则 f [ i + 1 ] '- f [ i ] 的费用比f[ i + 1 ] - f [ i ] 还小,所以f [ i + 1 ] '- f [ i ] 中有负环

所以这与假设矛盾,于是可以证明这种方法是正确的

 

 


 

【代码】

(基本上就是在最大流的基础上稍微改进了下)

// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cstring>
#define ll long long 
using namespace std;
const int MAXN = 50010;
const int MAXM = 100010;
const ll  INF = (1ll << 62) - 1;
typedef pair<int, int> P;
struct note
{
    int to;
    int nt;
    int rev;
    ll cost;
    ll cal;
};
struct edge
{
    note arr[MAXM];
    int  st[MAXN];
    ll  dis[MAXN];
    ll h[MAXN];
    int  cur[MAXN];
    int  depth[MAXN];
    int pre[MAXN];
    int pree[MAXN];
    int  top;
    int n, m, s, t;
    edge()
    {
        memset(st, -1, sizeof(st));
        memset(depth, -1, sizeof(depth));
        memset(dis, -1, sizeof(dis));
        top = 0;
    }
    void read()
    {
        top = 0;
        scanf("%d%d%d%d", &n, &m, &s, &t);
        for (int i = 1; i <= m; i++)
        {
            int x, y;
            ll z,c;
            scanf("%d%d%lld%lld", &x, &y, &z,&c);
            add(x, y, z,c);
        }
    }
    bool dep()
    {
        queue<int> q;
        q.push(s);
        memset(depth, -1, sizeof(depth));
        depth[s] = 0;
        while (!q.empty())
        {
            int v = q.front(); q.pop();
            for (int i = st[v]; i != -1; i = arr[i].nt)
            {
                int to = arr[i].to;
                if (!arr[i].cal)
                    continue;
                if (depth[to] != -1)
                    continue;
                depth[to] = depth[v] + 1;
                q.push(to);
            }
        }
        return (depth[t] != -1);
    }
    void add(int x, int y, ll z,ll c)
    {
        top++; arr[top] = { y,st[x],top + 1,c,z }; st[x] = top;
        top++; arr[top] = { x,st[y],top - 1,-c,0 }; st[y] = top;
    }
    ll dfs(int now, ll val)
    {
        if (now == t || !val)
            return val;
        ll flow = 0;
        for (int& i = cur[now]; i != -1; i = arr[i].nt)
        {
            int to = arr[i].to;
            if (depth[to] != depth[now] + 1)
                continue;
            ll f = dfs(to, min(arr[i].cal, val));
            if (!f || !arr[i].cal)
                continue;
            flow += f;
            arr[i].cal -= f;
            arr[arr[i].rev].cal += f;
            val -= f;
            if (!val)
                return flow;
        }
        return flow;
    }
    ll dinic()
    {
        ll flow = 0;
        ll f;
        while (dep())
        {
            for (int i = 1; i <= n; i++)
                cur[i] = st[i];
            while (f = dfs(s, INF))
                flow += f;
        }
        return flow;
    }
    ll min_cost(ll f)
    {
        ll res = 0;
        while (f > 0)
        {
            fill(dis, dis + 1 + n, INF);
            priority_queue<P, vector<P>, greater<P>> q;
            dis[s] = 0;
            q.push(P(0, s));
            while (!q.empty())
            {
                P p = q.top(); q.pop();
                int v = p.second;
                if (dis[v] < p.first) continue;
                for (int i = st[v]; i != -1; i = arr[i].nt)
                {
                    note &e = arr[i];
                    if (e.cal > 0 && dis[e.to] > dis[v] + e.cost + h[v] - h[e.to])
                    {
                        dis[e.to] = dis[v] + e.cost + h[v] - h[e.to];
                        pre[e.to] = v;
                        pree[e.to] = i;
                        q.push(P(dis[e.to],e.to));
                    }
                }
            }
            if (dis[t] == INF)    return -1;
            for (int i = 0; i <= n; i++)
                h[i] += dis[i];
            ll d = f;
            //printf("2\n");
            for (int i = t; i != s; i = pre[i])
            {
                d = min(d, arr[pree[i]].cal);
            }
            f -= d;
            res += d * h[t];
            for (int i = t; i != s; i = pre[i])
            {
                arr[pree[i]].cal -= d;;
                int rev = arr[pree[i]].rev;
                arr[rev].cal += d;
            }
            //printf("3\n");
        }
        return res;
    }
};
edge road;
edge tmp;
int main()
{
    int T;
    road.read();
    tmp = road;
    ll f = tmp.dinic();
    printf("%lld ", f);
    printf("%lld", road.min_cost(f));
    return 0;
}
View Code

 

注意:因为求最大流之后网络会发生变化,所以需要把最开始的原图记录下来

     费用流的求取也要在原图上进行

 

转载于:https://www.cnblogs.com/rentu/p/11323530.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值