最小费用最大流(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
来更新流量和费用,直到找不到新的增广路径为止。最终返回的是最小总费用。
希望这个逐字解析能够帮助您更好地理解这段代码的工作原理。如果您有任何进一步的问题或需要更多解释,请随时告知!