最小费用最大流(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的布尔函数,它接受两个整数参数s和t,分别表示源点和汇点。 - 返回值: 函数返回一个布尔值,用于指示是否找到了从源点到汇点的增广路径。
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的整数函数,它接受两个整数参数s和t,分别表示源点和汇点。 - 返回值: 函数返回一个整数,表示最小总费用。
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来更新流量和费用,直到找不到新的增广路径为止。最终返回的是最小总费用。
希望这个逐字解析能够帮助您更好地理解这段代码的工作原理。如果您有任何进一步的问题或需要更多解释,请随时告知!