最小费用最大流模板(dijstra和spfa)

介绍

相比最大流,边上多了个单位流量的费用。假设正向边费用为c,那么反向边的费用就是-c。由于最大流不会改变,所以我们可以一直沿着费用最短路来增广,来得到最小费用。即把原来的dini中的bfs换成最短路算法。

几个概念

最小边覆盖:用最少的边覆盖图所有顶点(边集)
最大匹配:两两没有公共结点的边集合的子集(边集)
最大独立集:在G中最大两两不相连的顶点集。(点集)
最小顶点覆盖:G任意边都至少有一个端点属于S的最小集合。(点集)
|最大独立集| + |最小顶点覆盖| = |V|

实现

由于存在负边权,需要使用SPFA或Bellman来求最短路。注意,因为不像bfs求出的层次图,只会沿着一个方向走,最短路可能出现重复访问结点导致超时。所以要多维护一个vis数组来防止重复访问。
求出最短路后,只要对dini稍加修改就可以求最小费用最大流了。(其实最短路只起到一个指导流量方向作用)。

SPFA

以下是SPFA费用流

//SPFA
#define INF 0x3f3f3f3f
const int N = 2e5 + 10;
const int M = 2e5 + 10;
const double eps = 1e5;

struct edge {
    int ne, np, f, c;
};
edge ed[M];
int head[N];
int cur[N];
int si = 2;
int dis[N];
int arr[N];

void init() {
    si = 2;
    memset(head, 0, sizeof head);
    memset(cur, 0, sizeof cur);
}

void add(int u, int v, int f, int c) {
    ed[si] = edge{head[u], v, f, c};
    head[u] = si;
    cur[u] = head[u];
    si++;

    ed[si] = edge{head[v], u, 0, -c};
    head[v] = si;
    cur[v] = head[v];
    si++;
}
bool vis[N];
bool spfa(int s, int t, int n) {
    memset(dis, INF, sizeof dis);
    memset(vis, 0, sizeof vis);
    dis[s] = 0;
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    while(!q.empty()) {
        int cur = q.front();q.pop();
        vis[cur] = 0;
        for(int e = head[cur]; e; e = ed[e].ne) {
            if(dis[cur] + ed[e].c < dis[ed[e].np] && ed[e].f) {
                dis[ed[e].np] = dis[cur] + ed[e].c;
                if(!vis[ed[e].np]) {
                    vis[ed[e].np] = 1;
                    q.push(ed[e].np);
                }
            }
        }
    }
    return dis[t] != INF;
}

int dfs(int p, int t, int flo, ll &cost) {
    if(p == t) return flo;
    
    int delta = flo;
    vis[p] = 1;
    for(int &i = cur[p]; i; i = ed[i].ne) {
        int nt = ed[i].np;
        if(dis[nt] == dis[p] + ed[i].c && ed[i].f && !vis[nt]) {
            int d = dfs(nt, t, min(delta, ed[i].f), cost);
            delta -= d;
            cost += 1ll * d * ed[i].c;
            ed[i].f -= d; ed[i^1].f += d;
            if(delta == 0) break;
        }
    }
    cur[p] = head[p];
    vis[p] = 0;
    return flo - delta;
}

ll cost = 0;
ll dini(int s, int t, int n) {
    int mxf = 0;
    while(spfa(s, t, n)) {
        mxf += dfs(s, t, INF, cost);
    }
    return mxf; 
}

dijstra

由于关于spfa,它死了的原因,我们可以用dijstra来求最短路!
当然,这不意味着可以用dijstra来求含负边图的最短路。因为下面算法原理是利用上一个残余网络的最短路来更新下一个残余网络的最短路。所以最开始的网络费用的最短路还是要你先求出来的(初始化)。比较好的一点是,一般的费用网络最开始正向边费用都是非负数,而反向边由于是0流所以不通,那么就可以用dijstra初始化。否则如果一开始就有负的费用就要先用SPFA初始化,后面再用dijstra。
先定义一些概念以方便介绍下面的算法:

  1. 我们引入势的概念。假设(h(v))为结点(v)的势,势就是点的点权
  2. (d(e))表示边(e<u, v>)的长度。
  3. (D_d(u,v))代表当(u)(v)最短路径中每条边e的长度为d(e)时路径的长度。

为把负边转为正边,引入势后,设新的边长为(d'(e)=d(e)-h(v) + h(u))。那么从点源(s)到汇点(t)的路径长度就变为(D_{d'}(s, t) = D_d(s, t) + h(s) - h(t)),也就是只加上了一个常数,s到t的最短路并没有改变!如果选取适当的h,使得每条边e有(d'(e) geq 0),那不就可以把所有边变成非负的吗?

这里可以取(h(v)=s到v的最短距离)。因为有(h(v) leq h(u)+d(e)),所以(d'(e)=d(e)-h(v)+h(u) geq 0)。所以一开始要先跑一次最短路来求出(h)

假设(h)求出来了,然后利用它更新得到(d' (e)geq0)。并用dijstra跑出了和(d(e))情况下同样的最短路和(D_{d'}(s, v)),并对这条最短路进行了增广。就会导致残余网络发生改变。那么原来的(h)在新的残余网络下不一定好使了,因为新增加的一些反向边(e),可能导致(d'(e) < 0)。所以要更新(h)
我们目标是更新完(h)以后在新的残余网络中得到的所有(d'(e) geq 0)。如果令新的(h)(h'(v)=D_d(s, v)),那么对于那些被增广的边,就是新的残余网络中新增加的反向边,必定是最短路里的边,有(d'(e)=0);没被增广的边(d'(e)geq 0)不变。所以这里(h')就是满足条件的新的(h)
因为(D_{d'}(s, v) = D_d(s, v) + h(s) - h(v))(h(s)=0),所以有(h'(v)=D_d(s, v)=D_{d'}(s, v) + h(v))。所以每次更新(h(v))的时候只要加上(D_{d'}(s, v))即可

//dijstra(交洛谷模板题要开O2)
//不知道为什么洛谷模板题里不开O2会比spfa慢qwq
bool vis[N];
typedef pair<int ,int > PII;
priority_queue<PII, vector<PII>, greater<PII>> q;

bool dijstra(int s, int t, int n) { //除了这里和dfs中的一行,其它部分和SPFA的一模一样
    for(int i = 1; i <= n; i++) h[i] += dis[i]; //更新h
    memset(dis, INF, sizeof dis);
    dis[s] = 0; //dis[v]即D(s, v)
    q.push(make_pair(0, s));
    while(!q.empty()) {
        auto cp = q.top();q.pop();
        int cur = cp.second;
        if(dis[cur] < cp.first) continue;
        for(int e = head[cur]; e; e = ed[e].ne) {
            int nt = ed[e].np;
            if((dis[cur] + ed[e].c + h[cur] - h[nt] < dis[nt]) && ed[e].f) {
                dis[nt] = dis[cur] + ed[e].c + h[cur] - h[nt];
                q.push(make_pair(dis[nt], ed[e].np));
            }
        }
    }
    return dis[t] != INF;
}

int dfs(int p, int t, int flo, ll &cost) {
    if(p == t) return flo;
    int delta = flo;
    vis[p] = 1;
    for(int &i = cur[p]; i; i = ed[i].ne) {
        int nt = ed[i].np;
        if((dis[nt] == dis[p] + ed[i].c + h[p] - h[nt]) && ed[i].f && !vis[nt]) { //dis的值只起到指导作用,只用它来走最短路,并不使用其中的值。
            int d = dfs(nt, t, min(delta, ed[i].f), cost);
            delta -= d;
            cost += 1ll * d * ed[i].c;
            ed[i].f -= d; ed[i^1].f += d;
            if(delta == 0) break;
        }
    }
    cur[p] = head[p];
    vis[p] = 0;
    return flo - delta;
}


ll cost = 0;
ll dini(int s, int t, int n) {
    memset(h, 0, sizeof h);
    int mxf = 0;
    while(dijstra(s, t, n)) {
        mxf += dfs(s, t, INF, cost);
    }
    return mxf; 
}

参考:《挑战程序设计竞赛》

原文地址:https://www.cnblogs.com/limil/p/12886084.html