题目地址:
https://www.acwing.com/problem/content/180/
给定一张 N N N个点(编号 1 , 2 … N 1,2…N 1,2…N), M M M条边的有向图,求从起点 S S S到终点 T T T的第 K K K短路的长度,路径允许重复经过点或边。注意:每条最短路中至少要包含一条边。
输入格式:
第一行包含两个整数
N
N
N和
M
M
M。接下来
M
M
M行,每行包含三个整数
A
A
A,
B
B
B和
L
L
L,表示点
A
A
A与点
B
B
B之间存在有向边,且边长为
L
L
L。最后一行包含三个整数
S
S
S,
T
T
T和
K
K
K,分别表示起点
S
S
S、终点
T
T
T和第
K
K
K短路。
输出格式:
输出占一行,包含一个整数,表示第
K
K
K短路的长度,如果第
K
K
K短路不存在,则输出
−
1
−1
−1。
数据范围:
1
≤
S
,
T
≤
N
≤
1000
1≤S,T≤N≤1000
1≤S,T≤N≤1000
0
≤
M
≤
105
0≤M≤105
0≤M≤105
1
≤
K
≤
1000
1≤K≤1000
1≤K≤1000
1
≤
L
≤
100
1≤L≤100
1≤L≤100
这里需要注意两点:
1、每条最短路中至少要含一边,所以如果起点终点重合的话,不含边的那条长度为
0
0
0的路径是不能算进去的。
2、着重要注意排除掉第
K
K
K短路不存在的情况。下面的算法内可以解决这个问题。
用A*算法。思路参考https://blog.csdn.net/qq_46105170/article/details/114877098,那里证明了当终点第一次出堆的时候,数对的第一维就是最短路距离。本题要求第
K
K
K短路,那么就可以猜测终点如果是第
K
K
K次出堆,第一维存的就是第
K
K
K短路。这个结论是正确的,证明如下:
数学归纳法。当
K
=
1
K=1
K=1结论成立。当
K
<
n
K<n
K<n的时候假设结论也成立,当
K
=
n
K=n
K=n的时候, 这里先要排除一下终点第
K
−
1
K-1
K−1次出堆之后再也没出堆过的情况,此时说明不存在第
K
K
K短路;如果终点第
K
K
K次出堆,但是堆空,则说明第
K
K
K短路的走法是唯一的,那显然结论成立;如果终点第
K
K
K次出堆,且堆不空,那么我们证明此时的
d
d
d里存的一定是第
K
K
K短路的长度,如果不然,那么其一定大于第
K
K
K短路长度,而堆里一定存在真实第
K
K
K短路的某个顶点还没出堆,其
d
+
h
d+h
d+h的值是小于等于真实第
K
K
K短路的长度的,那么它应该先出堆,这就矛盾了。
对于这题来说,如果起点终点重合,那么要求的第 K K K短路实际上要求成是第 K + 1 K+1 K+1短路,即要包含边数为 0 0 0的那条。此外就是启发函数 h ( x ) h(x) h(x)的选取,我们可以直接取成 x x x到终点的实际最短路距离,这个距离可以通过建反向图然后用Dijkstra算法来求。然后再按正向图,从起点做A*算法。当终点第 K K K次出堆的时候,第一维就是第 K K K短路距离。但是,由于可能不存在第 K K K短路,我们需要先判断一下起点是否能到达终点,并且在扩展的时候,要将那些与终点不连通的点排除掉。这样如果第 K K K短路不存在,就会使得堆提前变空(因为堆里的点一定都是能走到终点的点),这样就能识别出来了。
代码如下:
#include <iostream>
#include <cstring>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, PII> PIII;
const int N = 1010, M = 2 * 100000 + 10;
int n, m, S, T, K;
// h存正向边,rh存反向边
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N];
// Dijkstra算法里的st数组,用于标记某个点最短路是否已经被算出来过
bool st[N];
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);
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({0, T});
dist[T] = 0;
while (!heap.empty()) {
auto t = heap.top();
heap.pop();
int v = t.y;
if (st[v]) continue;
st[v] = true;
for (int i = rh[v]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[v] + w[i]) {
dist[j] = dist[v] + w[i];
heap.push({dist[j], j});
}
}
}
}
int astar() {
// 如果起点到不了终点,则说明不存在路径,直接返回-1
if (dist[S] == 0x3f3f3f3f) return -1;
priority_queue<PIII, vector<PIII>, greater<PIII> > heap;
heap.push({dist[S], {0, S}});
int cnt = 0;
while (!heap.empty()) {
auto t = heap.top();
heap.pop();
int v = t.y.y, dis = t.y.x;
if (v == T) cnt++;
// 如果是第K次出堆,则返回其d值
if (cnt == K) return dis;
for (int i = h[v]; ~i; i = ne[i]) {
int j = e[i];
// 略过走不到终点的点
if (dist[j] == 0x3f3f3f3f) continue;
heap.push({dis + w[i] + dist[j], {dis + w[i], j}});
}
}
return -1;
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 0; i < m; i++) {
int a, b, c;
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;
}
时间复杂度 O ( K E log V ) O(KE\log V) O(KElogV),空间 O ( K V ) O(KV) O(KV)。