首页 > 编程语言 >Johnson 全源最短路算法以及 Primal-Dual 原始对偶算法

Johnson 全源最短路算法以及 Primal-Dual 原始对偶算法

时间:2024-07-23 16:17:39浏览次数:8  
标签:势能 Johnson int Primal 短路 算法 nw 复杂度 dis

Johnson 全源最短路算法

引入:多源最短路问题,设点数为 \(n\) 边数为 \(m\)。

我们有如下方案:

  • floyd,时间复杂度 \(O(n^3)\),适合任意图。
  • Bellman-ford(SPFA),时间复杂度 \(O(n^2m)\),适合任意图。
  • Dijkstra,时间复杂度 \(O(nm\log m)\),适合非负权图。

综上分析,我们发现:Dijkstra 的时间复杂度最优,但适用范围较小,不能处理负权图。

那有没有办法使得 Dijkstra 能在负权图上运行呢?可以。

考虑给每个点设置一个势能 \(h_i\),它具有如下性质(摘自 OI Wiki):

势能还有一个特点,势能的绝对值往往取决于设置的零势能点,但无论将零势能点设置在哪里,两点间势能的差值是一定的。

我们随便令一个点为虚拟起点,以该点为起点跑 Bellman-ford,虚拟起点到每个点的最短路即为每个点的势能。

在最短路问题中,我们令一条有向边 \(u\rightarrow v\) 的原权值是 \(w\),那么在新图中他的权值被修改为 \(w+h_u-h_v\),使得最终的最短路大小只跟起点和终点的势能有关,而与路径上经过的其他点无关,且权值全部被修改为了正权值,那么就直接套用 Dijkstra 即可,时间复杂度 \(O(nm+nm\log m)\)。

正确性证明:

  • 求得的最短路大小与路径上的其他点无关:假设起点为 \(s,t\),令中间经过的点依次为 \(u_1,u_2,\cdots,u_k\),权值依次为 \(w_1,w_2,\cdots,w_{k+1}\),那么最终求得的最短路为 \(w_0+h_s-h_{u_1}+w_1+h_{u_1}-h_{u_2}+\cdots+w_{k+1}+h_{u_k}-h_{t}=w_0+w_1+\cdots+w_{k+1}+h_s-h_t\)。
  • 所有边的权值均非负:因为初始的势能是最短路求得的,而最短路满足三角形不等式 \(h_u+w\ge h_v\),也即 \(w+h_u-h_v\ge 0\)。

Primal-Dual 原始对偶算法

或许前面只是引子?

前置知识:基本费用流算法(好像叫 SSP?),Johnson 全源最短路算法。

众所周知,基本的费用流算法是每次用最短路算法找出一条边权最小的增广路径,直到原图不存在增广路径为止。由于费用流建图上会有反边(也即边权为负数的边),所以不能用 Dijkstra 求解最短路,只能使用 Bellman-ford 或者 SPFA,时间复杂度 \(O(nmf)\),\(f\) 为最大流。

但有些题目会丧心病狂的卡 SPFA,这时候基本的费用流算法会 TLE。

这个时候我们需要使用 Johnson 全源最短路算法的思想来把边权转化为非负权,从而可以使用 Dijkstra 求最短路,把时间复杂度优化至 \(O(fm\log m)\)。

仍然先跑一遍 Bellman-ford,势能的设置同 Johnson。但每次增广的话,图的形态会发生改变,那么新势能该如何设置呢?

设增广后从起点(也就是源点)的最短路为 \(dis_i\),那么我们只需要将每个点的势能加上 \(dis_i\) 即可。

正确性证明:

  • 求得的最短路大小与路径上的其他点无关:同理。
  • 所有边的权值均非负:考虑因为增广使得多出了一些边 \(v\rightarrow u\),那么一定满足 \(dis_v=dis_u+w+h_u-h_v\)(否则 \(u\rightarrow v\) 不在增广路上),因为初始的 \(w+h_u-h_v\ge 0\),所以有 \(dis_u\ge dis_v\),所以 \(w+dis_u+h_u-dis_v-h_v\ge 0\)。

时间复杂度 \(O(nm+fm\log m)\)。如果初始边权非负那么自然不需要初始化,时间复杂度可直接优化为 \(O(fm\log m)\)(因为初始反边没有流量所以不会进行增广)。

参考代码(省略初始化代码):

int S,T;
struct edge{
	int to,nxt,w,c;
}a[maxm];
int head[maxn],edges;
void add(int x,int y,int z,int w){
	a[++edges]=(edge){y,head[x],z,w};
	head[x]=edges;
}
void add_edge(int x,int y,int z,int w){
	add(x,y,z,w),add(y,x,0,-w);
}
int dis[maxn],dinic[maxn];
int h[maxn];
int pre[maxn],e[maxn];
bool dij(){
	rep(i,1,T)dis[i]=dinic[i]=llinf,vis[i]=0;
	priority_queue<pii,vector<pii>,greater<pii> >q;
	dis[S]=0,q.push(mp(0,S));
	while(!q.empty()){
		pii nw=q.top();q.pop();
		if(vis[nw.se])continue;vis[nw.se]=1;
		graph(i,nw.se,head,a){
			int u=a[i].to,exdis=a[i].c+h[nw.se]-h[u];
			assert(!a[i].w||exdis>=0);
			if(a[i].w&&dis[u]>nw.fi+exdis){
				dis[u]=nw.fi+exdis,dinic[u]=min(dinic[nw.se],a[i].w),pre[u]=nw.se,e[u]=i;
				q.push(mp(dis[u],u));
			}
		}
	}
	return vis[T];
}
int EK(){
	int res=0,flow=0;
	while(dij()){
		flow+=dinic[T];
		assert(h[S]==0);
		res+=dinic[T]*(dis[T]+h[T]);
		rep(i,1,T)h[i]+=dis[i];
		int p=T;
		while(p^S){
			a[e[p]].w-=dinic[T],a[e[p]^1].w+=dinic[T];
			p=pre[p];
		}
	}
	return res;
}

例题:ABC214H Collecting

标签:势能,Johnson,int,Primal,短路,算法,nw,复杂度,dis
From: https://www.cnblogs.com/dcytrl/p/18318737

相关文章

  • 代码随想录算法训练营第20天 | 二叉搜索树中级
    2024年7月22日题235.二叉搜索树的最近公共祖先普通解法还是和普通的祖先一样求即可,再依据搜索树特性进行剪枝即可加速。importjava.util.*;classSolution{Vector<TreeNode>vec1;Vector<TreeNode>vec2;intflag1=0;intflag2=0;publicT......
  • 简单架构:采集库dll、检测算法dll、项目程序exe,框架库dll
    一般项目exe通过调用各种封装的dll来完成工作。视觉项目exe调用采集库dll、检测算法dll就可以了,有一定积累后凝练出框架库dll(日志、队列、线程池等必不可少的部分封装)它们之间通过“接口函数+数据”来配合。针对采集dll:IGrabber.h里放接口函数,如开始采集、停止采集、set参数......
  • 【论文解读】大模型算法发展
    一、简要介绍 论文研究了自深度学习出现以来,预训练语言模型的算法的改进速度。使用Wikitext和PennTreebank上超过200个语言模型评估的数据集(2012-2023年),论文发现达到设定性能阈值所需的计算大约每8个月减半一次,95%置信区间约为5到14个月,大大快于摩尔定律下的硬......
  • 代码随想录算法训练营第18天 | 二叉搜索树进阶
    2024年7月20日题530.二叉搜索树的最小绝对差使用递归获取中序遍历,然后遍历一遍vector即可得到结果。importjava.util.*;classSolution{Vector<Integer>vec;publicintgetMinimumDifference(TreeNoderoot){//首先得到中序遍历的结果vec......
  • # 代码随想录算法训练营第38天 | 01背包问题:1049.最后一块石头的重量II(最多能装多少)、
    1049.最后一块石头的重量IIhttps://leetcode.cn/problems/last-stone-weight-ii/代码随想录https://programmercarl.com/1049.最后一块石头的重量II.html#算法公开课494.目标和https://leetcode.cn/problems/target-sum/description/代码随想录https://programmercarl.com......
  • 目标检测算法
    目标检测算法是计算机视觉领域中的一项核心技术,旨在从图像或视频中识别和定位一个或多个特定对象实例。这些算法不仅需要确定对象的位置(如通过边界框),还需要识别对象的类别(如人、汽车、狗等)。随着深度学习技术的快速发展,基于深度神经网络的目标检测算法已成为主流,并在各种应......
  • 离线分治算法:cdq 分治
    \(\texttt{0x00}\):前置芝士归并排序;树状数组;重载运算符(这个大概都会吧)。\(\texttt{0x01}\):介绍cdq分治是一种离线分治算法,可用来处理以下几种问题:解决和点对有关的问题。1D动态规划的优化与转移。通过CDQ分治,将一些动态问题转化为静态问题。它们的本质都是通过一......
  • Python-深度学习算法实用指南-全-
    Python深度学习算法实用指南(全)原文:zh.annas-archive.org/md5/844a6ce45a119d3197c33a6b5db2d7b1译者:飞龙协议:CCBY-NC-SA4.0前言深度学习是人工智能领域最受欢迎的领域之一,允许你开发复杂程度各异的多层模型。本书介绍了从基础到高级的流行深度学习算法,并展示了如何使用......
  • Andrew 算法求凸包
    Andrew算法求凸包参考资料:右手定则(baidu.com)内积和外积-OIWiki(oi-wiki.org)\(a\)与\(b\)相对位置\(b\)在\(a\)的逆时针方向:\(a\timesb>0\)顺负逆正(其实就是高中数学对于正负角的定义)凸包-OIWiki(oi-wiki.org)排序后最小和最大的元素为什么一......
  • 算法-代码随想录-哈希表
    有效的字母异位词思路:数组作为一个简单的哈希表,可以用来记录字符串中每个字符出现的次数。可以设置一个数组,第一轮遍历s中的字符,字符每出现一次,在数组对应位置+1。第二轮遍历t中的字符,字符每出现一次,在数组对应位置-1。最后遍历作为哈希表的数组,如果都为0,则说明每个字符出现的......