费用流
暴力
int vis[N], lst[N]; ll dis[N], flow[N]; int SPFA() { rep(i, 1, n) dis[i] = INF; queue<int> q; q.push(s), dis[s] = 0, flow[s] = INF; while (!q.empty()) { int u = q.front(); q.pop(), vis[u] = 0; for (int i = h[u]; i; i = e[i].n) { int v = e[i].t; if (e[i].w && dis[v] > dis[u] + e[i].c) { dis[v] = dis[u] + e[i].c; lst[v] = i, flow[v] = min(flow[u], e[i].w); if (!vis[v]) q.push(v), vis[v] = 1; } } } return dis[t] < INF; } ll F = 0, C = 0; while (SPFA()) { ll ret = flow[t]; F += ret, C += ret * dis[t]; for (int u = t; u != s; u = e[lst[u] ^ 1].t) e[lst[u]].w -= ret, e[lst[u] ^ 1].w += ret; }
原始对偶算法
英文:Primal-Dual
用处:用 Dijkstra 跑费用流,不支持负环
用 Johnson 里面的技巧,给每个点定义 ,边 的新边权为
为了让新边权 ,以源点为起始点跑最短路后 即可。
每次增广后,令 即可。
int vis[N]; ll H[N]; void SPFA() { memset(vis, 0, sizeof(vis)); memset(H, 0x3f, sizeof(H)); vis[s] = 1, H[s] = 0; queue<int> q; q.push(s), vis[s] = 1; while (!q.empty()) { int u = q.front(); q.pop(), vis[u] = 0; for (int i = h[u]; i; i = e[i].n) { int v = e[i].t; if (e[i].w && H[v] > H[u] + e[i].c) { H[v] = H[u] + e[i].c; if (!vis[v]) q.push(v), vis[v] = 1; } } } } priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> q; ll dis[N], flow[N]; int lst[N]; void Dijkstra() { memset(vis, 0, sizeof(vis)); rep(i, 1, n) dis[i] = INF; dis[s] = 0, flow[s] = INF; q.push({0ll, s}); while (!q.empty()) { int u = q.top().second; ll d = q.top().first; q.pop(); if (d > dis[u]) continue; for (int i = h[u]; i; i = e[i].n) { int v = e[i].t; if (e[i].w && dis[v] > dis[u] + e[i].c) { dis[v] = dis[u] + e[i].c; lst[v] = i; flow[v] = min(flow[u], e[i].w); q.push({dis[v], v}); } } } } ll F = 0, C = 0; SPFA(); while (1) { Dijkstra(); if (dis[t] == INF) break; ll ret = flow[t]; F += ret, C += ret * dis[t]; for (int u = t; u != s; u = e[lst[u] ^ 1].t) e[lst[u]].w -= ret, e[lst[u] ^ 1].w += ret; rep(i, 1, n) H[i] += dis[i]; } cout << F << '\n' << C << '\n';
单纯形网络流
英文:Simplex
用处:在线求最小费用最大流(支持动态加边),支持负环,效率高
思路:在图中不断找负环,然后把他跑满。
他和线性规划中的单纯形法类似,以下是他的步骤:
- 加边 ,容量 ,费用 (而非 )
- 随便找到一棵生成树,满足每条树边都有剩余流量。
- 枚举每一条边,判断他加入后是否产生负环,如果不产生负环就跳过。
- 找到这条边两端点在树上的 LCA,并找出环上流量最小的边,存下整个环。
- 更新环上流量、答案费用,删去流量最小边,加入这条边,再调整边顺序,构成新的生成树。
- 回到第 3 步直到没有负环为止。
- 最大流是第 1 步所加边的流量,最小费用要加上最大流 。
如何快速判负环?每个点维护到根路径和即可 判。
正确性:由第 1 步保证了。
注意到他的步骤和“LCT维护最小生成树”有点类似。
- 最小费用可行流:第 1 步加边的费用为 、反边容量为 ,第 7 步最小费用不变即可。
- 最小费用最小流:事先
swap(s, t)
即可。
int nw, fa[N], fe[N], cir[N], tag[N]; ll sum[N]; void DFS(int u, int f, int c) { fa[u] = e[f].u, fe[u] = f, tag[u] = c; for (int i = h[u]; i; i = e[i].n) if (e[i].w && tag[e[i].v] != c) DFS(e[i].v, i, c); } ll Sum(int u) { if (tag[u] == nw) return sum[u]; return tag[u] = nw, sum[u] = Sum(fa[u]) + e[fe[u]].c; } ll Push(int o) { int cnt = 0, del = 0, P = 2; ll C = 0, F = e[o].w; ++nw; int p = e[o].u, lca = e[o].v; while (p) tag[p] = nw, p = fa[p]; while (tag[lca] != nw) tag[lca] = nw, lca = fa[lca]; for (int u = e[o].u; u != lca; u = fa[u]) { cir[++cnt] = fe[u]; if (F > e[fe[u]].w) F = e[fe[u]].w, del = u, P = 0; } for (int u = e[o].v; u != lca; u = fa[u]) { cir[++cnt] = fe[u] ^ 1; if (F > e[fe[u] ^ 1].w) F = e[fe[u] ^ 1].w, del = u, P = 1; } cir[++cnt] = o; rep(i, 1, cnt) C += F * e[cir[i]].c, e[cir[i]].w -= F, e[cir[i] ^ 1].w += F; if (P == 2) return C; int u = e[o].u, v = e[o].v; if (P == 1) swap(u, v); int lste = o ^ P, lstu = v, tmp; while (lstu != del) { swap(fe[u], lste ^= 1), --tag[u]; tmp = fa[u], fa[u] = lstu, lstu = u, u = tmp; } return C; } void Simplex() { eadd(t, s, INF, -INF); DFS(t, 0, ++nw); tag[t] = ++nw, fa[t] = 0; ll F = 0, C = 0; int ok = 1; while (ok) { ok = 0; rep(i, 2, tot) if (e[i].w && e[i].c + Sum(e[i].u) - Sum(e[i].v) < 0) C += Push(i), ok = 1; } F = e[tot].w; C += e[tot].w * INF; cout << F << '\n' << C << '\n'; }
本文作者:Laijinyi
本文链接:https://www.cnblogs.com/laijinyi/p/18544607
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步