【問題描述】
給出一個圖G和指定的源點s、匯點t,求圖中從點s到點t的第K短路。
【具體題目】
PKU2449(注意:本題有一個猥瑣之處:不允許空路徑。也就是當s等于t時要將K值加1)
【算法】
本題有一種最樸素的辦法:改造Dijkstra,一開始路徑(s, 0)(這里設路徑(i, l)表示從s到i的一條長度為l的路徑)入隊。然后,
每次取隊中長度最短的路徑,該路徑(i, l)出隊,可以證明,若這是終點為i的路徑第x次出隊,該路徑一定是圖中從s到i的第x短路(若x>K則該路徑已無用,舍棄)。然后從點i擴展,將擴展到的路徑全部入隊。這樣直到終點為t的路徑第K次出隊即可。
該算法容易實現(借助priority_queue),但時間復雜度可能達到O(MK),需要優化。
優化:容易發現該算法其實有A*的思想,或者說,該算法
其實是所有結點的估價函數h()值均為0的A*算法。為了優化此題,需要將h()值改大。顯然,h(i)值可以設為
從i到t的最短路徑長度(容易證明它是一致的),然后g(i)=目前結點代表的路徑長度,f(i)=g(i)+h(i),然后A*即可。
注意:更改路徑條數應該在出隊時更改,而不能在入隊時更改,因為可能在該路徑出隊之前會有新的比它更短的路徑入隊。
代碼(PKU2449):
#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
const int MAXN = 1500, MAXM = 150000, INF = ~0U >> 2;
struct edge {
int kk, len, next;
} ed[MAXM], ed2[MAXM];
int n, m, s, t, k_, hd[MAXN], tl[MAXN], hd2[MAXN], tl2[MAXN], h[MAXN], q[MAXN + 1], No[MAXN], res = -1;
bool vst[MAXN];
struct qnode {
int i, g;
};
typedef priority_queue <qnode, vector<qnode> > pq;
pq z;
bool operator< (qnode q1, qnode q2)
{
return q1.g + h[q1.i] > q2.g + h[q2.i];
}
void init()
{
int a0, b0, l0;
scanf("%d%d", &n, &m);
re(i, n) hd[i] = tl[i] = hd2[i] = tl2[i] = -1;
re(i, m) {
scanf("%d%d%d", &a0, &b0, &l0); a0--; b0--;
ed[i].kk = b0; ed[i].len = l0; ed[i].next = -1;
if (hd[a0] == -1) hd[a0] = tl[a0] = i; else tl[a0] = ed[tl[a0]].next = i;
ed2[i].kk = a0; ed2[i].len = l0; ed2[i].next = -1;
if (hd2[b0] == -1) hd2[b0] = tl2[b0] = i; else tl2[b0] = ed2[tl2[b0]].next = i;
}
scanf("%d%d%d", &s, &t, &k_); --s; --t; k_ += s == t;
}
void prepare()
{
re(i, n) {h[i] = INF; vst[i] = 0;} h[t] = 0; vst[t] = 1; q[0] = t;
int i, h0, j, h1;
for (int front=0, rear=0; !(!front && rear == n || front == rear + 1); front == n ? front = 0 : front++) {
i = q[front]; h0 = h[i];
for (int p=hd2[i]; p != -1; p=ed2[p].next) {
j = ed2[p].kk; h1 = h0 + ed2[p].len;
if (h1 < h[j]) {
h[j] = h1;
if (!vst[j]) {vst[j] = 1; if (rear == n) q[rear = 0] = j; else q[++rear] = j;}
}
}
vst[i] = 0;
}
}
void solve()
{
qnode q0; q0.i = s; q0.g = 0; z.push(q0);
re(i, n) No[i] = 0;
int i, d0, j, d1;
while (!z.empty()) {
i = z.top().i; d0 = z.top().g; z.pop();
if (No[i] >= k_) continue;
No[i]++;
if (i == t && No[i] == k_) {res = d0; break;}
for (int p=hd[i]; p != -1; p=ed[p].next) {
j = ed[p].kk; d1 = d0 + ed[p].len;
q0.i = j; q0.g = d1; z.push(q0);
}
}
}
void pri()
{
printf("%d\n", res);
}
int main()
{
init();
prepare();
solve();
pri();
return 0;
}