费用流
posted on 2025-05-06 13:26:01 | under | source
SPFA
首先明确基本原理,就是不断跑代价最小的增广路,一条边能走需要满足残余流量为正。
用 SPFA 实现,复杂度 \(O(knm)\)。\(k\) 为最大流。
dijskutra 原始对偶算法
很高大上对吧,其实就是加上势能来兼容负权边。
考虑每次跑最短路前设计一个势能 \(h\),将边权改为 \(w+h_u-h_v\)。
具体来说:初始设为 \(0\)(反正也不会走反边),然后每次跑完更新 \(h_u\gets h_u+dis_u\),\(dis_u\) 是最短路数组。
证明:
- 原来有残余流量:\(dis_u+w\ge dis_v\),移项 \(dis_u-dis_v+w\ge 0\)。
- 原来没有但是增广其反边:那么在最短路上所以 \(dis_u-dis_v+w=0\),反边边权为其相反数也是 \(0\),满足条件。
注意是加上而不是直接赋值,毕竟我们是在原有势能得到的边权上跑最短路。
复杂度 \(O(km\log n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define pir pair<int, int>
const int N = 5e3 + 5, M = 5e4 + 5, INF = 1.5e9;
int n, m, u, v, c, f, tot = 1, head[N], S, T;
int dis[N], h[N], vis[N], pre[N];
struct edge{int u, v, nxt, cost, f;} e[M << 1];
inline void add(int u, int v, int cost, int f) {e[++tot] = {u, v, head[u], cost, f}, head[u] = tot;}
inline void span(int u, int v, int cost, int f) {add(u, v, cost, f), add(v, u, -cost, 0);}
inline pir mcmf(int S, int T){
int mxf = 0, mic = 0;
for(int i = 1; i <= n; ++i) vis[i] = h[i] = pre[i] = 0, dis[i] = INF;
while(1){
priority_queue<pir, vector<pir>, greater<pir> > q;
q.push({dis[S] = 0, S});
while(!q.empty()){
int u = q.top().second; q.pop();
if(vis[u]) continue; vis[u] = true;
for(int i = head[u]; i; i = e[i].nxt){
if(!e[i].f) continue;
int v = e[i].v, w = e[i].cost + h[u] - h[v];
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w, pre[v] = i;
q.push({dis[v], v});
}
}
}
if(dis[T] == INF) break;
int cc = INF, cs = 0;
for(int i = T; i != S; i = e[pre[i]].u) cc = min(cc, e[pre[i]].f), cs += e[pre[i]].cost;
mxf += cc, mic += cc * cs;
for(int i = T; i != S; i = e[pre[i]].u) e[pre[i]].f -= cc, e[pre[i] ^ 1].f += cc;
for(int i = 1; i <= n; ++i) h[i] += dis[i], dis[i] = INF, vis[i] = 0;
}
return {mxf, mic};
}
signed main(){
cin >> n >> m >> S >> T;
for(int i = 1; i <= m; ++i) scanf("%d%d%d%d", &u, &v, &f, &c), span(u, v, c, f);
pir ans = mcmf(S, T);
printf("%d %d\n", ans.first, ans.second);
return 0;
}

浙公网安备 33010602011771号