食用前请先了解 SPFA + Dinic/EK 求解 MCMF。


Sol.

总所周知,SPFA 牺牲了。于是我们寻求一些更稳定的算法求解 MCMF。

网络流算法的时间属于玄学,暂且判定为混乱中的稳定。那么我们就只能考虑在最短路算法上寻求优化。于是就想到了 Dijkstra。

但 Dijkstra 有一个致命的弱点:无法处理负权边。而我们应用的场景显然含有负权。

开动脑筋想一想可以想到一个“给所有边权加上巨大多权值进而规避负权边”的方法。

但这样在实现中,还需要记录一条最短路目前经过了哪些边之类的奇怪信息。不是说不可做,但确实复杂。

那我们考虑把这样累加的权值放在点上?就有了正解想法:在点上叠加势能。

这当然不是什么基于经典力学与计算机科学的逻辑思想统一能广泛应用的以简化实现提高效率推动人类社会发展促进世界和平为目的最新学科交叉成果。

它只是和“势能”同名,仅此而已。

具体的讲,我们考虑将每一个点附上一个权值 \(h\),并将 \(u, v\) 两点间的边 \((u, v)\) 的权值替换为 \(w_{u, v} + h_u - h_v\)。

在这张图上,对于一条起点为 \(s\) 终点为 \(t\) 的路径边集 \(D\),边权和为 \(\sum \limits _{(u, v) \in D} (w_{u, v} + h_u - h_v) = h_s - h_t + \sum \limits _{(u, v) \in D} w_{u, v}\)。

也就是说我们可以通过这张图上的最短路长度还原原图的最短路长度,且两图的路径一定一一对应。

容易发现,如果所有的 \(w_{u, v} + h_u - h_v\) 均大于等于 \(0\),即 \(w_{u, v} + h_u \geq h_v\),那么我们就可以用 Dijkstra 来解决问题。

好像构造 \(h\) 成为了麻烦事,不过我们貌似可以用一些现成的量来“充数”。

观察上面的不等式,发现是一个类三角不等式,而在最短路问题中也存在这样的三角不等式,即:记 \(f_x\) 表示起点 \(s\) 到点 \(x\) 的最短路长度,则满足 \(f_u + w_{u, v} \geq f_v\)。这和上面的式子形式一模一样!

而 Dinic/EK 是需要跑很多次最短路的,所以我们可以自然想到将上一次的最短路答案当作这一次的 \(h\),注意这里指的最短路是原图中的最短路而不是被势能改过的最短路,要注意区分。

那么此问题就结了。

最后一点就是说,势能的初值通常设 \(0\) ,而这并不一定能满足第一次 Dijkstra 直接就能跑。所以我们需要先跑一个 SPFA 去求出初始的势能(也还是等于最短路)。

只有一只 SPFA 牺牲了不会让整个代码都牺牲。

Code.

「Luogu P3381」这里是 EK 实现。

#include <queue>
#include <cstdio>
using namespace std; int Abs(int x) { return x < 0 ? -x : x; }
int Max(int x, int y) { return x > y ? x : y; }
int Min(int x, int y) { return x < y ? x : y; } int read() {
int x = 0, k = 1;
char s = getchar();
while(s < '0' || s > '9') {
if(s == '-')
k = -1;
s = getchar();
}
while('0' <= s && s <= '9') {
x = (x << 3) + (x << 1) + (s ^ 48);
s = getchar();
}
return x * k;
} void write(int x) {
if(x < 0) {
x = -x;
putchar('-');
}
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
} void print(int x, char s) {
write(x);
putchar(s);
} const int MAXN = 5e3 + 5;
const int MAXM = 5e4 + 5;
const int INF = 2147483647; struct edge {
int v, nxt, Wei, Cap, Flow;
edge() {}
edge(int V, int Nxt, int C, int W, int F) {
v = V, nxt = Nxt, Cap = C, Wei = W, Flow = F;
}
} e[MAXM << 1];
int head[MAXM << 1], cnt = 0;
void Add_Edge(int u, int v, int c, int w) {
e[cnt] = edge(v, head[u], c, w, 0);
head[u] = cnt++;
e[cnt] = edge(u, head[v], 0, -w, 0);
head[v] = cnt++;
} queue<int> q;
bool vis[MAXN];
int Dist[MAXN], Aug[MAXN], h[MAXN], n, m; struct Back {
int Pre, id;
Back() {}
Back(int P, int Id) {
Pre = P, id = Id;
}
} Last[MAXN]; struct node {
int x, dis;
node() {}
node(int X, int Dis) {
x = X, dis = Dis;
}
friend bool operator < (node One, node TheOther) {
return One.dis > TheOther.dis;
}
}; void spfa(int s, int t) {
for(int i = 1; i <= n; i++)
h[i] = INF, vis[i] = false;
h[s] = 0, vis[s] = true;
queue<int> q;
q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop();
vis[u] = false;
for(int i = head[u], v; ~i; i = e[i].nxt) {
v = e[i].v;
if(e[i].Cap - e[i].Flow > 0 && h[v] > h[u] + e[i].Wei) {
h[v] = h[u] + e[i].Wei;
if(!vis[v])
vis[v] = true, q.push(v);
}
}
}
} bool Dijkstra(int s, int t) {
for(int i = 1; i <= n; i++)
Dist[i] = INF, Last[i] = Back(-1, -1), vis[i] = false, Aug[i] = INF;
priority_queue<node> q;
Dist[s] = 0;
q.push(node(s, Dist[s]));
while(!q.empty()) {
int u = q.top().x; q.pop();
if(vis[u])
continue;
vis[u] = true;
for(int i = head[u], v; ~i; i = e[i].nxt) {
v = e[i].v;
if(e[i].Cap - e[i].Flow > 0 && Dist[v] > Dist[u] + e[i].Wei + h[u] - h[v]) {
Last[v] = Back(u, i);
Dist[v] = Dist[u] + e[i].Wei + h[u] - h[v];
Aug[v] = Min(Aug[u], e[i].Cap - e[i].Flow);
q.push(node(v, Dist[v]));
}
}
}
return Dist[t] != INF;
} int Flow, Cost;
void EK(int s, int t) {
Flow = 0, Cost = 0;
spfa(s, t);
while(Dijkstra(s, t)) {
Flow += Aug[t];
Cost += Aug[t] * (Dist[t] + h[t]);
int pos = t;
while(pos != s) {
e[Last[pos].id].Flow += Aug[t];
e[Last[pos].id ^ 1].Flow -= Aug[t];
pos = Last[pos].Pre;
}
for(int i = 1; i <= n; i++)
if(h[i] < INF)
h[i] += Dist[i];
}
} int main() {
n = read(), m = read();
int s = read(), t = read();
for(int i = 1; i <= n; i++)
head[i] = -1;
for(int i = 1, u, v, c, w; i <= m; i++) {
u = read(), v = read(), c = read(), w = read();
Add_Edge(u, v, c, w);
}
EK(s, t);
print(Flow, ' '), print(Cost, '\n');
return 0;
}

Note -「Dijkstra 求解 MCMF」的更多相关文章

  1. Note -「圆方树」学习笔记

    目录 圆方树的定义 圆方树的构造 实现 细节 圆方树的运用 「BZOJ 3331」压力 「洛谷 P4320」道路相遇 「APIO 2018」「洛谷 P4630」铁人两项 「CF 487E」Touris ...

  2. Note -「Dsu On Tree」学习笔记

    前置芝士 树连剖分及其思想,以及优化时间复杂度的原理. 讲个笑话这个东西其实和 Dsu(并查集)没什么关系. 算法本身 Dsu On Tree,一下简称 DOT,常用于解决子树间的信息合并问题. 其实 ...

  3. Note -「狄利克雷前缀和」

    学到一个诡异东西,当个 Trick 处理用吧. 现在有一个形如 \(\sum \limits _{i = 1} ^{n} \sum \limits _{d | i} f(d)\) 的柿子,不难发现可以 ...

  4. Note -「矩阵树定理」学习笔记

      大概--会很简洁吧 qwq. 矩阵树定理   对于无自环无向图 \(G=(V,E)\),令其度数矩阵 \(D\),邻接矩阵 \(A\),令该图的 \(\text{Kirchhoff}\) 矩阵 \ ...

  5. Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门

      进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...

  6. Note -「Lagrange 插值」学习笔记

    目录 问题引入 思考 Lagrange 插值法 插值过程 代码实现 实际应用 「洛谷 P4781」「模板」拉格朗日插值 「洛谷 P4463」calc 题意简述 数据规模 Solution Step 1 ...

  7. Note -「Mobius 反演」光速入门

    目录 Preface 数论函数 积性函数 Dirichlet 卷积 Dirichlet 卷积中的特殊函数 Mobius 函数 & Mobius 反演 Mobius 函数 Mobius 反演 基 ...

  8. Note -「动态 DP」学习笔记

    目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...

  9. LibreOJ #6014. 「网络流 24 题」最长 k 可重区间集

    #6014. 「网络流 24 题」最长 k 可重区间集 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据   ...

随机推荐

  1. 手摸手,带你实现移动端H5瀑布流布局

    移动端瀑布流布局是一种比较流行的网页布局方式,视觉上来看就是一种像瀑布一样垂直落下的排版.每张图片并不是显示的正正方方的,而是有的长有的短,呈现出一种不规则的形状.但是它们的宽度通常都是相同的 因为移 ...

  2. 『现学现忘』Git基础 — 26、给Git命令设置别名

    目录 1.什么是Git命令的别名 2.别名的全局配置 3.别名的局部配置 4.删除所有别名 5.小练习 1.什么是Git命令的别名 Git中命令很多,有些命令比较长,有些命令也不好记,也容易写错. 例 ...

  3. 多线程05:unique_lock详解

    unique_lock详解 一.unique_lock取代lock_guard unique_lock是个类模板,实际应用中,一般lock_guard(推荐使用):lock_guard取代了mutex ...

  4. uniapp复制到剪贴板

    uni.setClipboardData() ; 例: 给元素添加点击事件 <view @click="doCopy()">复制</view> 复制方法 d ...

  5. 一款高速的NET版的离线免费OCR

    PaddleOCR.Onnx 一款基于Paddle的OCR,项目使用ONNX模型,速度更快.本项目同时支持X64和X86的CPU上使用.本项目是一个基于PaddleOCR的C++代码修改并封装的.NE ...

  6. mapstruct 的 mapstruct-processor 自动生成的 Impl 文件中未设置属性值(时好时坏)

    配置依赖和注解处理器 ... <properties> <org.mapstruct.version>1.4.2.Final</org.mapstruct.version ...

  7. 好客租房1-React基础目标

    学习目标 能够说出React是什么 掌握react的特点 掌握react的基本使用 能够使用react脚手架 学习目录 react概述 react基本使用 react脚手架

  8. ElasticSearch7.3学习(二十七)----聚合概念(bucket和metric)及其示例

    一.两个核心概念:bucket和metric 1.1 bucket 有如下数据 city name  北京 张三  北京 李四 天津 王五 天津 赵六 天津 王麻子 划分出来两个bucket,一个是北 ...

  9. python之模块(os、sys、json、subprocess)

    目录 os模块 sys模块 json模块 subprocess模块 os模块 os模块主要是与操作系统打交道. 导入os模块 import os 创建单层文件夹,路径必须要存在 os.mkdir(路径 ...

  10. Fast-Rcnn学习笔记

    Fast-Rcnn学习笔记 paper code Fast-RCNN总览 step1:图片先放进卷积层 step2:再卷积层的特征图谱上回映射出对应的感兴趣区域 step3:集过一层ROI Pooli ...