【模板】K短路的稳定复杂度求法

本文详细介绍了求解有向图中第K短路径的问题,包括暴力搜索、搜索剪枝、启发式搜索(A*算法)及可持久化可并堆等方法。特别深入探讨了可持久化可并堆算法,通过构建最短路树和维护非树边序列,实现了高效求解。代码实例丰富,适合算法学习者深入理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

描述

给出一个有向图,你需要求出SSSTTT的所有路径中权值第kkk小的路径的权值。

这里的第kkk小是指,将所有本质不同的路径的权值排序后(不去重),从小到大排名为kkk

数据保证KKK短路一定存在。

数据卡A∗A*A以及乱搞+A∗A*A

输入

第一行两个整数n,mn,mn,m表示点数和边数。

然后mmm行三个整数ui,vi,wiu_i,v_i,w_iui,vi,wi表示m条边。

然后三个整数k,s,tk,s,tk,s,t。表示求第kkk短路,起点为sss终点为ttt

输出

输出一个整数表示kkk短路

样例输入

2 2
1 2 5
2 1 4
2 1 2

样例输出

14

提示

保证答案不会爆int
1≤n≤1e5,1≤m≤3e5,1≤k≤500001≤n≤1e5,1≤m≤3e5,1≤k≤500001n1e5,1m3e5,1k50000


解析:

我们先从最基础的暴力开始

1.朴素搜索

搜出所有路径,排序后找第kkk大。

显然不行。

2.搜索剪枝

搜出KKK条路径,用大根堆维护,然后继续搜索的时候如果遇到已经比当前第KKK小大了就剪枝。不然搜出的新的路径就直接丢进堆当中,然后弹堆顶。

复杂度仍然不行,随机数据就能卡掉,而且这个剪枝策略不能用在有负权的图上。

3.启发式搜索

就是我们常说的A∗A*A

建反图,在反图上跑出目标点TTT到所有点的最短路。

然后搜索用的估价函数就直接设置为最短路距离。

然后用一个堆来维护,从起点BFSBFSBFS,把每次扩展的路径全部丢进堆当中。
每次取堆顶进行扩展。当我们第KKK次扩展出的到TTT的路径就是答案。

欧皇可以尝试这种算法,把所有边和点的顺序随机打乱后有奇效。

不过对于特殊的图,怎么打乱都是卡不过去的。

最好复杂度O(nlog⁡n+(K+m)log⁡m)O(n\log n+(K+m)\log m)O(nlogn+(K+m)logm)

不过随机数据就已经不是这个复杂度了,很容易卡。

但是,一般出题人不会真的闲得蛋疼卡A∗A*A,所以如果您只是想学A∗A*A的话,建议您找一篇专门讲启发式搜索的博客或论文,两分钟就能学会的东西。

4.可持久化可并堆

在我学习这个做法的那篇博客中,博主给了这种做法一个总结概括性的描述:

这个是在A∗算法上和最短路算法上,总结升华提取出来的一个较为稳定的K短路算法。

首先我们想一想为什么A*跑得慢?

因为最短路有相当多的信息可以利用,但是A*只利用了距离来做估价函数。

现在考虑怎么得到一个不是那么短的路,接下来将“不那么短的路”称为欠短路径。

我们已经求出了最短路了。也就是说,我们至少现在又最短路树和最短路DAGDAGDAG可以利用。

我们选择结构更为简单的最短路树。构建出任意一棵最短路树,树根为TTT

然后剩下一些非树边。考虑任意一条非树边&lt;u,v,w&gt;&lt;u,v,w&gt;<u,v,w>,我们从SSS走到vvv后再走到www显然就能得到一条欠短路径。

所以我们考虑所有合法的非树边的序列,我们只需要将这个这个序列中的第KKK小找出来就行了。

首先我们重新定义一条非树边&lt;u,v,w&gt;&lt;u,v,w&gt;<u,v,w>的权值为dist[v]+w−dist[u]dist[v]+w-dist[u]dist[v]+wdist[u],那么我们只需要考虑这个序列的权值之和就能确定当前路径的实际权值了,就是dist[S]+valdist[S]+valdist[S]+val

那么现在考虑怎么构造出合法的非树边序列。

首先如果说我们经过了某一个点uuu的话,那么u→Tu\rightarrow TuT这段路径上的非树边我们至少可以选择一条,然后根据实际的决策我们再选择第二条以及更多条。

所以,我们用一个小根堆来维护一下每个节点uuu在到TTT的树上路径上所有非树边的权值就行了。

考虑怎么快速求出这个小根堆。

每个点的小根堆可以从它的父亲那里继承过来,然后插入所有连接着自己的非树边。

记录权值的同时还需要记录一下这条边连向的点,才能转移到其他节点那里进行加入非树边的操作。

然后又是A∗A*A的思想。我们用一个堆维护当前扩展出的所有非树边序列。

初始时候只有次短路径。
每次从堆顶取当前最小的进行扩展。取出的第K−1K-1K1个就是答案。

扩展非树边序列的具体做法就是,在堆上向左右儿子扩展,同时向当前堆顶边连向的节点进行扩展。

具体实现看一下代码就明白了。


代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		cs static int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

cs int N=100005,M=300005,logM=18,INF=0x3f3f3f3f;
int n,m,k,S,T;

struct Graph{
	int last[N],nxt[M],to[M],w[M],ecnt;
	inline void addedge(int u,int v,int val){
		nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v,w[ecnt]=val;
	}
}g,rg;

int dist[N];

inline void Dij(){
	memset(dist,0x3f,sizeof dist);
	set<pair<int,int> > q;
	q.insert(make_pair(dist[T]=0,T));
	cs int *last=rg.last,*nxt=rg.nxt,*w=rg.w,*to=rg.to;
	while(!q.empty()){
		re int u=q.begin()->second;q.erase(q.begin());
		for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
			if(dist[v]>dist[u]+w[e]){
				q.erase(make_pair(dist[v],v));
				dist[v]=dist[u]+w[e];
				q.insert(make_pair(dist[v],v));
			}
		}
	}
}

bool tree_edge[M],vis[N];
int fa[N],st[N],top;

void dfs(int u){
	vis[u]=true;
	st[++top]=u;
	for(int re e=rg.last[u],v=rg.to[e];e;v=rg.to[e=rg.nxt[e]])
	if(!vis[v]&&dist[v]==dist[u]+rg.w[e]){
		fa[v]=u;tree_edge[e]=true;
		dfs(v);
	}
}

namespace LT{
	int son[M*logM][2];
	int ht[M*logM],val[M*logM],id[M*logM];
	int tot;
	
	inline int newnode(int _val,int _id,int _dis=0){
		re int now=++tot;
		val[now]=_val,id[now]=_id;
		ht[now]=_dis,son[now][0]=son[now][1]=0;
		return now;
	}
	
	inline int _copy(int ori){
		re int now=++tot;
		val[now]=val[ori],id[now]=id[ori];
		ht[now]=ht[ori],son[now][0]=son[ori][0],son[now][1]=son[ori][1];
		return now;
	}
	
	inline int merge(int a,int b){
		if(!a||!b)return a|b;
		if(val[a]>val[b])swap(a,b);
		int now=_copy(a);
		son[now][1]=merge(son[now][1],b);
		if(ht[son[now][0]]<ht[son[now][1]])swap(son[now][0],son[now][1]);
		ht[now]=ht[son[now][1]]+1;
		return now;
	}
	
	inline void insert(int &rt,int val,int id){
		rt=merge(newnode(val,id),rt);
	}
}

int rt[M];

inline void build_heap(){
	int *last=g.last,*nxt=g.nxt,*to=g.to,*w=g.w;
	for(int re i=1;i<=top;++i){
		re int u=st[i];
		rt[u]=rt[fa[u]];
		for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]])
		if(!tree_edge[e]&&dist[v]!=INF)LT::insert(rt[u],dist[v]-dist[u]+w[e],v);
	}
}

inline int solve_K(){
	__gnu_pbds::priority_queue<pair<int,int> ,greater<pair<int,int> > > q;
	q.push(make_pair(dist[S]+LT::val[rt[S]],rt[S]));
	while(!q.empty()){
		pair<int,int> qq=q.top();q.pop();
		if((--k)==1)return qq.first;
		int v=qq.first,u=qq.second;
		int lc=LT::son[u][0],rc=LT::son[u][1],o=LT::id[u];
		if(rt[o])q.push(make_pair(v+LT::val[rt[o]],rt[o]));
		if(lc)q.push(make_pair(v+LT::val[lc]-LT::val[u],lc));
		if(rc)q.push(make_pair(v+LT::val[rc]-LT::val[u],rc));
	}
	assert(0);
}

signed main(){
	n=getint();m=getint();
	for(int re i=1;i<=m;++i){
		int u=getint(),v=getint(),val=getint();
		g.addedge(u,v,val);
		rg.addedge(v,u,val);
	}
	k=getint(),S=getint(),T=getint();
	if(S==T)++k;
	Dij();
	if(k==1){
		cout<<dist[S];
		return 0;
	}
	dfs(T);
	build_heap();
	cout<<solve_K();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值