给定一张N个点(编号1,2…N),M条边的有向图,求从起点S到终点T的第K短路的长度,路径允许重复经过点或边。
注意: 每条最短路中至少要包含一条边。
输入格式
第一行包含两个整数N和M。
接下来M行,每行包含三个整数A,B和L,表示点A与点B之间存在有向边,且边长为L。
最后一行包含三个整数S,T和K,分别表示起点S,终点T和第K短路。
输出格式
输出占一行,包含一个整数,表示第K短路的长度,如果第K短路不存在,则输出“-1”。
数据范围
1≤S,T≤N≤1000
0≤M≤105
1≤K≤1000
1≤L≤100
输入样例:
2 2
1 2 5
2 1 4
1 2 2
输出样例:
14
思路:A*算法就是在bfs上加了一个启发函数,也就是一个距离的估值,从而使搜索更直接,更准确,避免了许多不必要的搜索,A*算法的一条定理,只要估计值f(s)<= 实际距离d(s),每次从优先队列中bfs之前取出的用来扩展的就是当前最小距离,因为最短距离一定<=真实距离,所以可以:
1、用终点T作为dijkstra的起点在反向图中跑一遍dijkstra算出每个点到终点的最短距离,作为那个点的估值。
2、用astar算法,然后根据估值排序(把估计值作为pair的first放到优先队列就可以实现按估值排序了),从S开始bfs,每次的取出的就是最短,第k次到达T点就是第K短路了。
完整代码:
#include <iostream>
#include <cstring>
#include <queue>
#define pll pair<int,int>
#define plll pair<int,pll>
#define x first
#define y second
using namespace std;
const int maxm=2e5+5,maxn=1e3+5;
int head[maxn],e[maxm],net[maxm],w[maxn],to[maxm],rhead[maxn];
int S,T,K;
int cnt,n,m,dis[maxn],vis[maxn],f[maxn];//f[i]记录到达编号为i的点估计的距离(本题取的最短距离),所以f=dis
void addEdge(int *head,int a,int b,int l)
{
e[cnt]=b;
net[cnt]=head[a];
w[cnt]=l;
head[a]=cnt++;
}
void dijkstra()
{
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
priority_queue<pll,vector<pll>,greater<pll> >heap;//小根堆(每个元素为pair类型,放在一个vector里)
heap.push({0,T});//从终点开始往回扩展,由此计算出每个点距离T的最短距离作为估值f
dis[T]=0;//dijkstra算法,起点的dis初始值为0!
while(heap.size()){
pll t=heap.top();
heap.pop();
int u=t.y;
if(vis[u]) continue;
vis[u]=1;
for(int i=rhead[u];~i;i=net[i]){
int v=e[i];
if(dis[u]+w[i]<dis[v]){
dis[v]=dis[u]+w[i];
heap.push({dis[v],v});//由此计算出每个点v距离T的最短距离作为估值f
}
}
}
}
int astar()
{
memcpy(f,dis,sizeof dis);
memset(vis,0,sizeof vis);
priority_queue<plll,vector<plll>,greater<plll> >heap;//小根堆(每个元素为pair<int,pair>(即:pair<int,<int,int>>)类型,放在一个vector里)
heap.push({f[S],{0,S}});//first存放当前点的估值f(估计距离),priority_queue按first这个估计值排序(从小到大),second.first为当前点的真实距离,second.second为当前点的编号
while(heap.size()){
plll t=heap.top();//按估计值排序后,每次取出当前加上估值最小的距离,根据A*算法的定理,这样就是从当前到最后的最短距离遍历的所有距离的最小值(一开始是最短,第二次为第二短...),然后进行扩展
heap.pop();
int u=t.y.y,d=t.y.x;
vis[u]++;
if(u==T&&vis[u]==K) return d;//当前是第K次到达T这个点,那当前距离就是到达T的第K短距离
for(int i=head[u];~i;i=net[i]){
int v=e[i];
heap.push({d+w[i]+f[v],{d+w[i],v}});//每次扩展时,当前最短距离+边权+估值作为下一次的估值,当前真实距离d+w[i],当前点编号v
}
}
return -1;
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
memset(head,-1,sizeof head);
memset(rhead,-1,sizeof rhead);
scanf("%d%d",&n,&m);
while(m--){
int a,b,l;
scanf("%d%d%d",&a,&b,&l);
addEdge(head,a,b,l);
addEdge(rhead,b,a,l);
}
scanf("%d%d%d",&S,&T,&K);
if(S==T) K++;//当S==T时,最短是自己到自己的,但该方案没有边,这里不算做一条最短路,所以除去这条,下条最短算作第一短,应该是求k+1短
dijkstra();
int ans=astar();
cout<<ans<<endl;
return 0;
}