图论 —— 网络流 —— 费用流 —— 基于 Dijkstra 的费用流

【概述】

在求解费用流时,大多数情况都是使用基于 SPFA 的 MCMF 算法,但有时某些毒瘤题会卡 SPFA,此时就要利用基于 Dijkstra 的费用流来求解。

【算法原理】

基于 Dijkstra 的费用流实质上就是在 MCMF 中代替 SPFA 来找增广路径,由于 Dijkstra 只能处理负边权图,因此在求费用流时,需要对图进行处理,来保证边权非负

引入一个势能函数 h[x],Dijkstra 每进行松弛操作时,加入势能函数来进行相应的考虑,其引入势能函数的过程,本质上就是 Johnson 算法对边重赋值的过程

即对于 u、v 两点,原来的松弛操作是:dis[u]>dis[v]+w(u,v)

引入势能函数后有:dis[u]>dis[v]+w(u,v)+h[u]-h[v]

因此只需要保证 w(u,v)+h[u]-h[v] 是正的,那么整张图就不存在负边权

下面考虑如何构造势能函数 h[x]:

设 disG 为原图第 i 次增广后的 disG 数组,那么考虑原图最短路的松弛:对于某一条边 w(u,v),有:disG[u]>=disG[u]+w(u,v),即 disG[u]-disG[v]>=w(u,v)

因此可令势能函数 h[u]=disG[u],h[v]=disG[v],其中 disG[u] 为原图上一次最短路松弛后的 dis 的值

因此,在每一轮的增广开始时前,h[i] 必须是上一次增广的 dis[i],故每次增广完成后,h[i] 要加上 dis[i],这样 h[i] 就变成了这次增广的 disG[i],以供下一次增广使用。

此外,需要注意的是,基于 Dijkstra 的费用流不能用链式前向星来写,要用 vector 来写,而且在 vector 的结构体中,要多一个维度 rev,以寻找反向弧,这样的好处是在增广时可以根据 preV、preE 两个前趋数组沿汇点回到起点。

【模版】

#define Pair pair<int,int>
struct Edge {
    int to;
    int cap, dis; //容量、费用
    int rev;      //(u,v)的反向弧中,v在u的位置
    Edge() {}
    Edge(int to, int cap, int dis, int rev): to(to), cap(cap), dis(dis), rev(rev) {}
};
vector<Edge> G[N];
struct Pre {  //记录前驱
    int node; //前驱结点
    int edge; //对应的边
} pre[N];
int h[N];   //势能函数
int dis[N]; //费用
void addEdge(int x, int y, int cap, int dis) {
    G[x].push_back(Edge(y, cap, dis, (int)G[y].size()));      //正向边
    G[y].push_back(Edge(x, 0, -dis, (int)(G[x].size() - 1))); //反向边
}
bool Dijkstra(int S, int T) {
    memset(dis, INF, sizeof(dis));
    dis[S] = 0;

    priority_queue<Pair, vector<Pair>, greater<Pair>> Q;
    Q.push(Pair(dis[S], S));
    while (!Q.empty()) {
        Pair now = Q.top();
        Q.pop();

        int u = now.second;
        if (dis[u] < now.first)
            continue;

        for (int i = 0; i < G[u].size(); i++) {
            int v = G[u][i].to;
            int cap = G[u][i].cap;
            int w = G[u][i].dis;
            if (cap && dis[v] > w + dis[u] + h[u] - h[v]) {
                dis[v] = w + dis[u] + h[u] - h[v]; //进行松弛
                pre[v].node = u;                   //记录前驱点
                pre[v].edge = i;                   //记录前驱边
                Q.push(Pair(dis[v], v));
            }
        }
    }

    if (dis[T] == INF)
        return false;
    else {
        for (int i = 0; i <= T + 1; i++) //对于势能函数,每次加上当前轮的dis
            h[i] += dis[i];
        return true;
    }
}
void maxFlow(int S, int T, int &flow, int &cost) {
    memset(h, 0, sizeof(h));
    memset(pre, 0, sizeof(pre));

    int newFlow = 0;                 //增广流量
    while (flow && Dijkstra(S, T)) { //当无法增广时,即找到答案
        int minn = INF;
        for (int i = T; i != S; i = pre[i].node) {
            int node = pre[i].node;
            int edge = pre[i].edge;
            minn = min(minn, G[node][edge].cap);
        }

        flow -= minn;        //原流量
        newFlow += minn;     //增广流量
        cost += h[T] * minn; //增广流花销

        for (int i = T; i != S; i = pre[i].node) {
            int node = pre[i].node;
            int edge = pre[i].edge;
            int rev = G[node][edge].rev;
            G[node][edge].cap -= minn;
            G[i][rev].cap += minn;
        }
    }
}
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    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;
    int flow = INF;//最大流量,根据题目设置
    int cost = 0;
    maxFlow(S, T, flow, cost);
    printf("%d\n", cost);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值