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

Johnson 全源最短路算法

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

我们有如下方案:

  • floyd,时间复杂度 O(n3),适合任意图。
  • Bellman-ford(SPFA),时间复杂度 O(n2m),适合任意图。
  • Dijkstra,时间复杂度 O(nmlogm),适合非负权图。

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

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

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

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

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

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

正确性证明:

  • 求得的最短路大小与路径上的其他点无关:假设起点为 s,t,令中间经过的点依次为 u1,u2,,uk,权值依次为 w1,w2,,wk+1,那么最终求得的最短路为 w0+hshu1+w1+hu1hu2++wk+1+hukht=w0+w1++wk+1+hsht
  • 所有边的权值均非负:因为初始的势能是最短路求得的,而最短路满足三角形不等式 hu+whv,也即 w+huhv0

Primal-Dual 原始对偶算法

或许前面只是引子?

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

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

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

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

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

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

正确性证明:

  • 求得的最短路大小与路径上的其他点无关:同理。
  • 所有边的权值均非负:考虑因为增广使得多出了一些边 vu,那么一定满足 disv=disu+w+huhv(否则 uv 不在增广路上),因为初始的 w+huhv0,所以有 disudisv,所以 w+disu+hudisvhv0

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

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

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 洛谷链接 题解

作者:dcytrl

出处:https://www.cnblogs.com/dcytrl/p/18318737

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   dcytrl  阅读(115)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示