浅谈费用流

前置知识

  • 最大流(关于最大流可以参考我的博客

就是在最大流的情况下求最小的花费(而最大费用最大流就可以把费用取成负数在跑最小费用最大流)。
于是我们就想到一个思路,在找增广路径的时候把找最短的增广路径改成找费用最小的增广路径。
正确的思路,就是这样的。
接下来就有两个思路

ZKW费用流

由NOI2008年金牌选手张昆玮发明

solution

首先我们先设个dis值,跟sap里的d值有点像,但这里的dis[i]表示的是终点到i点最短路径。
然后我们可以直接按照dis值找最短路径,比如说我想知道从i点走到j点是否是最短路径,就当dis[j]+i点到j点的距离==dis[j]的时候,这里走的一定是最短路径,否则就不是。
当找不到增广路的时候,说明你的dis值已经有问题了,于是我们考虑如何更改dis值。

我们先把节点分为两个部分,一个是S部,一个是T部。
S部就是从源点能靠dis值走到的点,剩下的点就是T部。
首先我们知道,我们将S部的点的dis值全部加上一个值,S部的性质还是满足的。
所以说,我们只需要找到一个值,使得S部加上这个值后,能与T部相连。
因为我们求的是最短路径,所以说我们就找一个S部的点到T部的点是dis需要增加的最小值。

然后我们接着跑最大流,直到从源点无法到汇点,也就是说,S部的点永远不可能与T部的点相连的时候,就找到答案了。

如何记录答案,就是将你增广的量乘上你一路上经过的费用的和,也就是从源点到汇点的最短路乘上增广量。

code

bool label() {
    int mindis = INF;
    for (int i = 1; i <= n; i++)
        if (vis[i])
            for (int j = last[i]; j; j = nxt[j])
                if (!vis[to[j]] && a[j])
                    mindis = min(w[j] + dis[to[j]] - dis[i], mindis);//求最小差值
    if (mindis == INF)
        return false;//如果没有一个边能从S部流向T部,就说明没有个增广路径了
    for (int i = 1; i <= n; i++)
        if (vis[i])
            dis[i] += mindis;//将S部的点的dis全部加上一个值
    return true;
}
int aug(int s, int augcc) {
    if (s == n) {
        ans += dis[1] * augcc;//将增广量乘于一路上的费用的和就是你所花费的代价
        return augcc;
    }
    int augc = augcc, delta, son;
    vis[s] = 1;
    for (int i = last[s]; i && augc; i = nxt[i]) {
        son = to[i];
        if (a[i] > 0 && !vis[son] && dis[s] == dis[son] + w[i]) {
            delta = aug(son, min(a[i], augc));
            a[i] -= delta, a[i ^ 1] += delta;//i^1是反向边,边是从2号边开始存储的
            augc -= delta;
        }
    }
    return augcc - augc;
}
void augca() {
    do {
        do {
            memset(vis, 0, sizeof(vis));
        } while (aug(1, INF));
    } while (label());//在找不到增广路径的情况下更新dis值
}

用SPFA找最短路径

solution

就是在每次找增广路径前,用SPFA求一遍最短路,然后在用最短路增广。
我们用dis值记录最短路,然后按照zkw费用流增广的方法增广。
也就是说,每次不合法的时候我们并不是在原有的dis值的基础上更改dis值,而是重新算一遍dis值。

code

bool spfa()
{
    queue<int> myq;
    myq.push(target);
    memset(dis,0x3f,sizeof dis);
    dis[target]=0,inq[target]=1;
    while(!myq.empty())
    {
        int t=myq.front();
        myq.pop(),inq[t]=0;
        for(int i=fir[t];i;i=e[i].next)
        {
            int v=e[i].v;
            if(e[i^1].cap>0)
                if(dis[v]>dis[t]+e[i^1].cost)
                {
                    dis[v]=dis[t]+e[i^1].cost;
                    if(inq[v]==0) myq.push(v),inq[v]=1;
                }
        }
    }
    if(dis[src]==0x3f3f3f3f)return 0;
    else return 1;
}
int aug(int s,int augco)
{
    if(s==target)
    {
        ans+=dis[src]*augco;
        return augco;
    }
    int tempcap=augco,delta;
    for(int i=fir[s];i&&tempcap>0;i=e[i].next)
    {
        if(!vis[e[i].v]&&e[i].cap>0)
        {
            if(dis[e[i].v]==dis[s]-e[i].cost)
            {
                vis[e[i].v]=1;
                delta=aug(e[i].v,min(e[i].cap,tempcap));
                vis[e[i].v]=0;
                tempcap-=delta;
                e[i].cap-=delta;
                e[i^1].cap+=delta;
            }
        }
    }
    return augco-tempcap;
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值