【概述】
在求解费用流时,大多数情况都是使用基于 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;
}