详情请看注释
参考代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1010, M = 2e5 + 10;
typedef pair<int, int> PII;
typedef pair<int, PII> PIII;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N], n, m, S, T, K;
bool vis[N];
/*
1. 用dijkstra求估值距离! ---> 建立反向边的图求终点到各个点的最短距离即可!
2. 使用A*算法求第k条最短路! ---> 终点第k次出队就是第k短路!
---> 证明同讲解八数码的终点以一次出队即为最短路的证明,可以使用数学归纳法完整证明!
注意:
1. 起点和终点一样是,需要特判,应求第K + 1条最短路,因为题目保证:每条最短路中至少要包含一条边。(0条边的最短路要被抛弃)
2. 要想求第k短路,因此就不能像普通写法一样,有更新时才入队,这里则需要全部入队,很容易理解!
3. 该A*算法可以添加一个优化,即 if(cnt[j] >= K) continue; 不加会直接TLE!
该优化正确性解释:(引用 @Arroganceの浓 的解释)
在有解时,要取得第K短路,每个点最多只能走不超过K次,如果超过K次的话,这个点每次都能走到终点,
那么我们就有 > K 条路可以到达终点, 所以这时到达的终点一定不是第K短路。
*/
void add(int h[], int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[T] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, T});
while(heap.size()){
auto t = heap.top();
heap.pop();
if(vis[t.second]) continue;
vis[t.second] = true;
for(int i = rh[t.second]; ~i; i = ne[i]){
int j = e[i];
if(dist[j] > dist[t.second] + w[i]){
dist[j] = dist[t.second] + w[i];
heap.push({dist[j], j});
}
}
}
}
int astar(){
// {当前距离 + 估值距离, {当前距离, 当前点编号}}
priority_queue<PIII, vector<PIII>, greater<PIII>> heap;
heap.push({0 + dist[S], {0, S}});
while(heap.size()){
auto t = heap.top();
heap.pop();
int ver = t.second.second, distance = t.second.first;
cnt[ver] ++;
if(cnt[T] == K) return distance;
for(int i = h[ver]; ~i; i = ne[i]){
int j = e[i];
// 剪枝优化
if(cnt[j] >= K) continue;
heap.push({distance + w[i] + dist[j], {distance + w[i], j}});
}
}
return -1;
}
int main(){
cin >> n >> m;
memset(h, -1, sizeof h), memset(rh, -1, sizeof rh);
for(int i = 0; i < m; i ++){
int a, b, w; cin >> a >> b >> w;
add(h, a, b, w), add(rh, b, a, w);
}
cin >> S >> T >> K;
// 如果起点和终点是一个点,则变成第K + 1条最短路,因为题目保证:每条最短路中至少要包含一条边。
if(S == T) K ++;
// 使用dijkstra算法求反向图终点到每个点的最短距离作为估值距离!
dijkstra();
cout << astar() << endl;
return 0;
}