第K短路[POJ2449]

本文介绍了一种使用A*算法求解有向图中第K短路的方法,通过反向求最短路得到估价函数,再利用A*算法的优先队列进行正向搜索,有效地优化了时间复杂度。

欢迎大家访问我的老师的OJ———caioj.cn

题面描述

给定 N N N个点, M M M条有向边,求第 K K K短路。
注意:若出发点与结束点为同一点,则一定要从出发点跑出去,再跑回来,才算最短路

思路

这道题如果直接优先队列跑的话,最坏时间复杂度为 O ( K ∗ ( N + M ) ∗ ( l o g ( N + M ) ) O(K*(N+M)*(log(N+M)) O(K(N+M)(log(N+M)),因为我们要跑 K K K次路径。
现提供 A ∗ A* A做法:
所谓 A ∗ A* A用人话描述就是目光要放长远脚步要够坚定,它好比有高人带着你刷副本似的,少走弯路。
比起普通优先队列, A ∗ A* A多了一个估价函数 f ( x ) f(x) f(x),为了保证最优解, f ( x ) f(x) f(x)要满足一个基本准则

  1. 设当前状态 state ⁡ \operatorname{state} state到目标状态所需代价的估计值为 f(state) ⁡ 。 \operatorname{f(state)}。 f(state)
  2. 实际所需价值为 g(state) ⁡ \operatorname{g(state)} g(state).
  3. 对于任意的 state ⁡ \operatorname{state} state,应该有 f(state) ⁡ ≤ g(state) ⁡ \operatorname{f(state)}\le \operatorname{g(state)} f(state)g(state)

这种带有估计函数的优先队列BFS就称为 A*(A Star) ⁡ \large \operatorname{A*(A~ Star)} A*(A Star).只要保证 f(state) ⁡ ≤ g(state) ⁡ \operatorname{f(state)}\le \operatorname{g(state)} f(state)g(state) ,A*就一定能在目标状态第一次从堆中取出时得到最优解,并且在搜索过程中每个状态只需要被拓展一次
下面给出简要证明:
设当前状态 S S S并不是最优解,
S S S的“当前代价”+ f ( S ) ⁡ ≥ \operatorname{\large f(S)}\ge f(S) 当前处在未来最优路径上的 T T T的“代价“+ f ( T ) ⁡ \operatorname{\large f(T)} f(T),所以 T T T先拓展, S S S则被忽略。

第K短路则运用了 A ∗ A* A的排除多余状态与优先队列 B F S BFS BFS,第几次取出 e d ed ed,即求到第几短路的性质来解决问题, A ∗ A* A主要起优化作用。

反向求从 e d ed ed跑到 s t st st的最短路,得到估价函数 f f f

后正常BFS即可。

代码

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int N=1e3+10;
const int inf=0x3f3f3f3f;
const int M=1e5+10;
struct edge{int x,y,d,next;}a[M],b[M];int len,last1[N],last2[N];
void ins(int x,int y,int d)
{
	a[++len].x=x,a[len].y=y;a[len].d=d;a[len].next=last1[x];last1[x]=len;
	b[len].x=y;b[len].y=x;b[len].d=d;b[len].next=last2[y];last2[y]=len;
}
struct nde
{
	int x,d;
	nde(){}
	nde(int x,int d):x(x),d(d){}
	bool operator <(const nde a)const{return d>a.d;}
};
int f[N];bool v[N];int st,ed;
void dijk_p()
{
	priority_queue<nde>q;
	memset(f,0x3f,sizeof(f));memset(v,false,sizeof(v));
	q.push(nde(ed,0));f[ed]=0;
	while(!q.empty())
	{
		nde t=q.top();q.pop();
		int x=t.x;if(v[x])continue;
		v[x]=true;
		for(int k=last2[x];k;k=b[k].next)
		{
			int y=b[k].y;
			if(f[y]>f[x]+b[k].d)
			{
				f[y]=f[x]+b[k].d;
				q.push(nde(y,f[y]));
			}
		}
	}
}
struct node
{
	int x,d,f;
	node(){}
	node(int x,int d,int f):x(x),d(d),f(f){}
	bool operator <(const node a)const{return f==a.f?d>a.d:f>a.f;}
};
int kl;
void dijk_AS()
{
	priority_queue<node>q;int cnt=0;
	if(f[st]==inf){puts("-1");return ;}
	q.push(node(st,0,f[st]));
	while(!q.empty())
	{
		node t=q.top();q.pop();
		if(t.x==ed)cnt++;
		if(kl==cnt){printf("%d\n",t.d);return ;}
		int x=t.x;
		for(int k=last1[x];k;k=a[k].next)
		{
			int y=a[k].y;
			int d=t.d+a[k].d;
			q.push(node(y,d,d+f[y]));
		}
	}
	puts("-1");
}
int main()
{
	int n,m;len=0;
	scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){int x,y, d;scanf("%d%d%d",&x,&y,&d);ins(x,y,d);}
    scanf("%d%d%d",&st,&ed,&kl);if(st==ed)kl++;
    dijk_p();
    dijk_AS();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值