最小费用最大流(MCMF)算法

66 阅读6分钟

最小费用最大流(MCMF)算法的代码将帮助您更深入地理解每个部分的作用。我们将从SPFA函数开始,然后是MCMF函数。

bool SPFA(int s,int t) {
    queue<int>q;
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    vis[s]=1;
    q.push(s);
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u]; ~i; i=e[i].next) {
            int v=e[i].to;
            if(e[i].cap>e[i].flow&&dis[v]>dis[u]+e[i].pay) {
                dis[v]=dis[u]+e[i].pay;
                pre[v]=i;
                if(vis[v])continue;
                vis[v]=1;
                q.push(v);
            }
        }
    }
    return pre[t]!=-1;
}
int MCMF(int s,int t) {
    int mincost=0;
    while(SPFA(s,t)) {
        int d=inf,v=t;
        while(v!=s) {
            int i=pre[v];
            d=min(d,e[i].cap-e[i].flow);//可增加的流量
            v=e[i^1].to;//换点
        }
        v=t;
        while(v!=s) {
            int i=pre[v];
            e[i].flow+=d;
            e[i^1].flow-=d;//实流
            v=e[i^1].to;
        }
        mincost+=dis[t]*d;
    }
    return mincost;
}

逐字解析:SPFA (Shortest Path Faster Algorithm)

bool SPFA(int s, int t) {
  • 定义: 定义一个名为SPFA的布尔函数,它接受两个整数参数st,分别表示源点和汇点。
  • 返回值: 函数返回一个布尔值,用于指示是否找到了从源点到汇点的增广路径。
    queue<int> q;
  • 定义队列: 创建一个整数类型的队列q,用于存储正在处理的节点。
    memset(vis, 0, sizeof(vis));
  • 初始化访问标记数组: 使用memset函数将vis数组的所有元素设置为0,表示所有节点尚未被访问过。
    memset(pre, -1, sizeof(pre));
  • 初始化前驱数组: 将pre数组的所有元素设置为-1,表示当前没有前驱节点。
    memset(dis, 0x3f, sizeof(dis));
  • 初始化距离数组: 将dis数组的所有元素设置为一个非常大的值(0x3f3f3f3f),表示从源点到各节点的距离初始为无穷大。
    dis[s] = 0;
  • 设置源点距离: 将源点s的距离设置为0,因为源点到自身的距离为0。
    vis[s] = 1;
  • 标记源点已访问: 将源点s标记为已访问,即vis[s]设置为1。
    q.push(s);
  • 入队源点: 将源点s加入队列q,准备开始搜索。
    while (!q.empty()) {
  • 主循环: 当队列q不为空时,继续执行以下操作。这表示还有未处理的节点。
        int u = q.front();
  • 取出队首节点: 从队列q中取出队首节点u,准备处理其相邻节点。
        q.pop();
  • 移除队首节点: 从队列q中移除刚刚取出的节点u
        vis[u] = 0;
  • 标记节点未访问: 将节点u标记为未访问,允许后续再次访问该节点(如果找到更短路径的话)。
        for (int i = head[u]; ~i; i = e[i].next) {
  • 遍历相邻边: 遍历与节点u相连的所有边。这里的head[u]指向与u相连的第一条边,e[i].next指向下一条边。~i确保索引i不是-1,即边存在。
            int v = e[i].to;
  • 获取目标节点: 获取边i的目标节点v
            if (e[i].cap > e[i].flow && dis[v] > dis[u] + e[i].pay) {
  • 检查并更新距离:
    • 检查边i是否有剩余容量(e[i].cap > e[i].flow)。
    • 如果通过边i到达节点v的总费用比当前记录的小(dis[v] > dis[u] + e[i].pay),则更新距离。
                dis[v] = dis[u] + e[i].pay;
  • 更新距离: 更新节点v的距离为dis[u] + e[i].pay,即通过边i到达v的费用。
                pre[v] = i;
  • 记录前驱边: 将到达节点v的边索引i记录在pre[v]中,以便后续回溯路径。
                if (vis[v]) continue;
  • 避免重复入队: 如果节点v已经被访问过(vis[v]为1),则跳过本次循环,不将其加入队列。
                vis[v] = 1;
  • 标记节点已访问: 将节点v标记为已访问,防止重复处理。
                q.push(v);
  • 入队目标节点: 将节点v加入队列q,准备后续处理。
        }
    }
  • 结束主循环: 当队列q为空时,结束主循环,表示所有可达节点都已处理完毕。
    return pre[t] != -1;
  • 返回结果: 如果汇点t有前驱边(pre[t]不等于-1),则返回true,表示找到了从源点到汇点的增广路径;否则返回false

逐字解析:MCMF (Minimum Cost Maximum Flow)

int MCMF(int s, int t) {
  • 定义: 定义一个名为MCMF的整数函数,它接受两个整数参数st,分别表示源点和汇点。
  • 返回值: 函数返回一个整数,表示最小总费用。
    int mincost = 0;
  • 初始化最小费用: 将mincost初始化为0,用于累加每次增广路径带来的额外费用。
    while (SPFA(s, t)) {
  • 主循环: 当SPFA(s, t)返回true时,即找到了从源点到汇点的增广路径,继续执行以下操作。
        int d = inf, v = t;
  • 初始化最大流量和当前节点:
    • d初始化为一个非常大的值inf,用于记录可以增加的最大流量。
    • v初始化为汇点t,用于从汇点开始回溯路径。
        while (v != s) {
  • 回溯路径: 当当前节点v不是源点s时,继续执行以下操作。
            int i = pre[v];
  • 获取前驱边: 获取到达节点v的边索引i
            d = min(d, e[i].cap - e[i].flow);
  • 确定最大流量: 计算可以通过边i增加的最大流量,取当前最大流量d和边i的剩余容量e[i].cap - e[i].flow中的较小值。
            v = e[i ^ 1].to;
  • 换点: 更新当前节点v为边i的反向边的起点e[i ^ 1].to,继续回溯路径。
        }
  • 结束回溯路径: 当回溯到源点s时,结束回溯路径。
        v = t;
  • 重置当前节点: 将当前节点v重新设置为汇点t,准备更新流量。
        while (v != s) {
  • 更新流量: 当当前节点v不是源点s时,继续执行以下操作。
            int i = pre[v];
  • 获取前驱边: 获取到达节点v的边索引i
            e[i].flow += d;
  • 增加正向边流量: 将边i的流量增加d
            e[i ^ 1].flow -= d;
  • 减少反向边流量: 将边i的反向边流量减少d,以保持流量守恒。
            v = e[i ^ 1].to;
  • 换点: 更新当前节点v为边i的反向边的起点e[i ^ 1].to,继续更新流量。
        }
  • 结束更新流量: 当更新到源点s时,结束更新流量。
        mincost += dis[t] * d;
  • 累加总费用: 将本次增广路径带来的额外费用dis[t] * d加到mincost上。
    }
  • 结束主循环: 当不再有增广路径时,结束主循环。
    return mincost;
  • 返回结果: 返回累计的最小总费用mincost

总结

这段代码实现了最小费用最大流(MCMF)算法,结合了最短路径(SPFA)和最大流的思想。SPFA函数负责寻找从源点到汇点的最短增广路径,而MCMF函数则通过不断调用SPFA来更新流量和费用,直到找不到新的增广路径为止。最终返回的是最小总费用。

希望这个逐字解析能够帮助您更好地理解这段代码的工作原理。如果您有任何进一步的问题或需要更多解释,请随时告知!