图论 —— 网络流 —— 费用流 —— zkw 费用流

【概述】

求解费用流的方法很多,目前最流行的是 MCMF 费用流,其实质是将 EK 算法中的 bfs 换为了 SPFA 来计算最小费用,但其存在的一个缺点是 EK 是单路增广的,这样速度会相应的慢一些

然后 zkw 神犇进行了改进,在 dfs 的时候实现多路增广,利用 KM 算法来代替 SPFA 算法来修改顶标,简单来说,其将 EK 费用流与 Dinic 费用流结合了起来。

【算法原理】

回忆一下 Dinic 算法找增广路的过程,该算法一遇到一条可以走的边就走,直到走出来一条源点到汇点的一条可行的路径

再回忆一下 EK 费用流(MCMF 费用流)的过程,先跑一遍 SPFA,使其找出一条流量不为 0 的具有最小费用的从源点到汇点的路径,然后每次做 SPFA 增广一条路径

对于 MCMF 来说,每次做 SPFA 只能增广一条路径,这无疑是极大的浪费

对于当前增广的路径来说,由于一条不在最短路图上的路径的费用肯定没有最短路图上的费用小,因此其肯定在源点的最短路图上,那么这个最短路图是可以一次 SPFA 跑出来的

这时,顺着最短路图做一次 Dinic 找增广路即可,此外,在做 SPFA 时,可以顺便进行分层,从而可以很好的利用 Dinic 的分层优化,SPFA 的 SLF 优化也可以加上

原版的 zkw 费用流没有利用 SPFA 算法,利用了 KM 算法来修改顶标,但这样无法处理具有负边权的情况,利用 SPFA 的 zkw 费用流的改进版则没有这种缺陷。

zkw 费用流的优点大于缺点,在数据量较大特别是增广路较多、较长的时候,优势十分明显,而当数据量较小时,常数十分大

原版的 zkw 费用流:点击这里

【源代码】

struct Edge {
    int to;
    int next;
    int cap, cost;
} edge[N];
int head[N], tot;
bool vis[N];
int dis[N], level[N];

void addEdge(int x, int y, int cap, int cost) {
    edge[++tot].to = y;
    edge[tot].next = head[x];
    edge[tot].cap = cap;
    edge[tot].cost = cost;
    head[x] = tot;

    edge[++tot].to = x;
    edge[tot].next = head[y];
    edge[tot].cap = 0;
    edge[tot].cost = -cost;
    head[y] = tot;
}

bool SPFA(int S, int T, int n) {
    memset(dis, INF, sizeof(dis));
    memset(vis, false, sizeof(vis));
    memset(level, 0, sizeof(level));
    dis[S] = 0;
    level[S] = 1;
    vis[S] = true;

    deque<int> Q;
    Q.push_back(S);
    while (!Q.empty()) {
        int x = Q.front();
        Q.pop_front();
        vis[x] = false;

        for (int i = head[x]; i != -1; i = edge[i].next) {
            int to = edge[i].to;
            if (dis[to] > dis[x] + edge[i].cost && edge[i].cap > 0) {
                dis[to] = dis[x] + edge[i].cost;
                level[to] = level[x] + 1;
                if (!vis[to]) {
                    vis[to] = true;
                    if (!Q.empty() && dis[to] < dis[Q.front()])
                        Q.push_front(to);
                    else
                        Q.push_back(to);
                }
            }
        }
    }
    return dis[T] != dis[n + 1];
}

bool flag = false;
int dfs(int x, int T, int t, int &flow, int &cost) {
    if (x == T) {
        flow += t;
        flag = true;
        return t;
    }
    int num = 0, newFlow = 0;
    for (int i = head[x]; i != -1; i = edge[i].next) {
        int to = edge[i].to;
        if (t == num)
            break;
        if (dis[x] + edge[i].cost == dis[to] && level[x] + 1 == level[to] && edge[i].cap > 0) {
            newFlow = dfs(to, T, min(t - num, edge[i].cap), flow, cost);

            num += newFlow;
            cost += newFlow * edge[i].cost;

            edge[i].cap -= newFlow;
            edge[i ^ 1].cap += newFlow;
        }
    }
    return num;
}
void zkw(int S, int T, int n) {
    int flow = 0, cost = 0;
    while (SPFA(S, T, n)) {
        flag = true;
        while (flag) {
            flag = false;
            dfs(S, T, INF, flow, cost);
        }
    }
    printf("%d %d\n", flow, cost);
}

int main() {
    int n, m;
    while (scanf("%d%d", &n, &m) != EOF) {
        memset(head, -1, sizeof(head));
        tot = 1;

        for (int i = 1; i <= m; i++) {
            int x, y, cap, cost;
            scanf("%d%d%d%d", &x, &y, &cap, &cost);
            addEdge(x, y, cap, cost);
        }
        int S=1,T=n;
        zkw(S, T, n);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值