題目描述:
給定一張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≤10^5,
1≤K≤1000,
1≤L≤100
輸入樣例:
2 2
1 2 5
2 1 4
1 2 2
輸出樣例:
14
分析:
在上一題八數碼問題中,我們詳細分析了A*算法的原理和實現。本題同樣是A*算法的應用,題目中求的第K短路,要求每條路徑至少要包含一條邊,所以當起點與終點重合時,要去掉本身這條長度爲0的自環邊,執行k++操作即可。另外第k短路這裏的k可以是重複的排名,比如k = 3時,那麼第二短的路徑有4條時,求的就是第2短的路徑長度了。
在上一題中,我們證明了A*算法中終點第一次出隊時的距離就是最短路徑距離,那麼是不是終點第k次出隊時的距離就是第k短路的距離呢,答案是肯定的,設終點t第二次出隊時到起點的距離是g,到終點的估計距離是0(因爲要不大於實際距離),隊列中任一個頂點離起點的距離是g1,到終點的估計距離是f,因爲終點出隊了,所以g < g1 + f,又f小於到終點的實際距離d,故g < g1 + d = 經過這個頂點從起點到終點路徑的長度,這就證明了當終點t第二次出隊時路徑長度已經是所有能到達終點路徑中最小的了(因爲終點已經出隊過一次了),同理可以證明t第k次出隊時的距離就是第k短路距離。因爲要求的倍數最短路徑長度,所以鬆弛操作時應該將每次的距離都更新下,不論是否變小,每次被更新時到起點的距離可以直接作爲隊列的一個屬性存入優先級隊列。
下面要考慮如何涉及估價函數,使得每個點的估計距離要小於等於到終點的實際距離,只需要從終點t倒着求一遍dijkstra算法,就可以求出每個點到終點的最短距離了,後面A*算法不論求的是第幾短距離到終點的實際距離都不會小於最短距離。題目中的圖是有向圖,所以不僅需要建立鄰接表,還要建立逆鄰接表方便倒着來一遍dijkstra。實現細節並不複雜,具體見代碼:
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1005,M = 200005;
typedef pair<int,int> PII;
typedef pair<int,pair<int,int> > PIII;
priority_queue<PII,vector<PII>,greater<PII> > q1;
priority_queue<PIII,vector<PIII>,greater<PIII> > q2;
int d[N],cnt[N];//到終點距離,出隊次數
bool st[N];
int k,n,m,s,t,h[N],rh[N],e[M],ne[M],w[M],idx;
void add(int *L,int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=L[a],L[a]=idx++;
}
void dijkstra(){
memset(d,0x3f,sizeof d);
d[t] = 0;
q1.push({0,t});
while(q1.size()){
int u = q1.top().second;
q1.pop();
if(st[u]) continue;
st[u] = true;
for(int i = rh[u];~i;i = ne[i]){
int j = e[i];
if(d[u] + w[i] < d[j]){
d[j] = d[u] + w[i];
q1.push({d[j],j});
}
}
}
}
int astar(){
q2.push({d[s],{0,s}});
while(q2.size()){
PIII t1 = q2.top();
q2.pop();
int dis = t1.second.first,u = t1.second.second;
cnt[u]++;
if(u == t && cnt[u] == k) return dis;
for(int i = h[u];~i;i = ne[i]){
int j = e[i];
q2.push({dis + w[i] + d[j],{dis + w[i],j}});
}
}
return -1;
}
int main(){
scanf("%d%d",&n,&m);
int a,b,c;
memset(h,-1,sizeof h);
memset(rh,-1,sizeof rh);
for(int i = 0;i < m;i++){
scanf("%d%d%d",&a,&b,&c);
add(h,a,b,c),add(rh,b,a,c);
}
scanf("%d%d%d",&s,&t,&k);
if(s == t) k++;
dijkstra();
printf("%d\n",astar());
return 0;
}