Note -「Dijkstra 求解 MCMF」
食用前请先了解 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」的更多相关文章
- Note -「圆方树」学习笔记
目录 圆方树的定义 圆方树的构造 实现 细节 圆方树的运用 「BZOJ 3331」压力 「洛谷 P4320」道路相遇 「APIO 2018」「洛谷 P4630」铁人两项 「CF 487E」Touris ...
- Note -「Dsu On Tree」学习笔记
前置芝士 树连剖分及其思想,以及优化时间复杂度的原理. 讲个笑话这个东西其实和 Dsu(并查集)没什么关系. 算法本身 Dsu On Tree,一下简称 DOT,常用于解决子树间的信息合并问题. 其实 ...
- Note -「狄利克雷前缀和」
学到一个诡异东西,当个 Trick 处理用吧. 现在有一个形如 \(\sum \limits _{i = 1} ^{n} \sum \limits _{d | i} f(d)\) 的柿子,不难发现可以 ...
- Note -「矩阵树定理」学习笔记
大概--会很简洁吧 qwq. 矩阵树定理 对于无自环无向图 \(G=(V,E)\),令其度数矩阵 \(D\),邻接矩阵 \(A\),令该图的 \(\text{Kirchhoff}\) 矩阵 \ ...
- Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门
进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...
- Note -「Lagrange 插值」学习笔记
目录 问题引入 思考 Lagrange 插值法 插值过程 代码实现 实际应用 「洛谷 P4781」「模板」拉格朗日插值 「洛谷 P4463」calc 题意简述 数据规模 Solution Step 1 ...
- Note -「Mobius 反演」光速入门
目录 Preface 数论函数 积性函数 Dirichlet 卷积 Dirichlet 卷积中的特殊函数 Mobius 函数 & Mobius 反演 Mobius 函数 Mobius 反演 基 ...
- Note -「动态 DP」学习笔记
目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...
- LibreOJ #6014. 「网络流 24 题」最长 k 可重区间集
#6014. 「网络流 24 题」最长 k 可重区间集 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 ...
随机推荐
- python 本地配置文件库 Dynaconf 简介
[前言] 在项目中经常会遇到以下几种需要用到配置文件的场景: 相同的配置参数用在不同的代码中,如果需要调整,则需要手动将各个使用到的地方都相应调整. 密码等信息不想硬编码在项目文件中. 配置文件的格式 ...
- Flutter 状态管理框架 Provider 和 Get 分析
文/ Nayuta,CFUG 社区 状态管理一直是 Flutter 开发中一个火热的话题.谈到状态管理框架,社区也有诸如有以 Get.Provider 为代表的多种方案,它们有各自的优缺点. 面对这么 ...
- Spring Ioc源码分析系列--Ioc的基础知识准备
Spring Ioc源码分析系列--Ioc的基础知识准备 本系列文章代码基于Spring Framework 5.2.x Ioc的概念 在Spring里,Ioc的定义为The IoC Containe ...
- PowerJob高级特效-容器部署完整教程
介绍 powerjob提供了容器功能,用来做一些灵活的任务处理.这里容器为 JVM 级容器,而不是操作系统级容器(Docker).(至于为什么取"容器"这个有歧义的名字是因为作者没 ...
- 力扣算法:125-验证回文串,131-分割回文串---js
LC 125-验证回文串 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 注:回文串是正着读和反着读都一样的字符串. ...
- IX交换中心网络架构分析
拓扑如上 IX功能介绍 IX交换中心,客户接入交换中心只收取端口费用,在交换中心网内的流量不收取任何费用,一个交换中心是否值得接入主要看该ix所接入的用户 假如客户A是做视频网站,用的视频源是IQY的 ...
- Arthas常用功能及一次线上问题排查
一.Arthas简介 Arthas是Alibaba开源的Java诊断工具,功能很强大,它是通过Agent方式来连接运行的Java进程.主要通过交互式来完成功能. https://arthas.aliy ...
- 构建AR视频空间大数据平台(物联网及工业互联网、视频、AI场景识别)
目 录 1. 应用背景... 2 2. 系统框架... 2 3. AI场景识别算法和硬件... 3 4. AR视频空间管理系统... 5 5. ...
- 安装Redis到Linux(源码)
运行环境 系统版本:Ubuntu 16.04.2 LTS 软件版本:redis-5.0.4 硬件要求:无 安装过程 1.配置系统参数 root@localhost:~# vim /etc/sysctl ...
- Docker容器编译安装Nginx
Docker容器编译安装Nginx,最简单的Nginx配置. 创建容器&进入容器 宿主机2080映射容器的80端口 [root@localhost ~]# docker run -i -d - ...