acwing-178. 第K短路

考点

  • Astar

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PI;
typedef pair<int, PI> PII;
const int maxn = 2e5 + 50;
int n, m, S, T, K, tot = -1, head[maxn], rhead[maxn], f[maxn];

struct node {
int nxt_, v_, w_;
} e[maxn << 1];

void add(int head[], int u, int v, int w) {
e[++tot].v_ = v, e[tot].w_ = w, e[tot].nxt_ = head[u];
head[u] = tot;
}

void dijkstra() {
bool vis[maxn];
memset(vis, 0, sizeof(vis)), memset(f, 0x3f, sizeof(f));
priority_queue<PI, vector<PI>> q;
q.push({0, T});
f[T] = 0;
while (!q.empty()) {
int u = q.top().second;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = rhead[u]; ~i; i = e[i].nxt_) {
int v = e[i].v_, w = e[i].w_;
if (f[u] + w < f[v]) {
f[v] = f[u] + w;
q.push({-f[v], v});
}
}
}
}

int astar() {
if (S == T) ++K;
priority_queue<PII, vector<PII>> q;
int cnt[maxn];
memset(cnt, 0, sizeof(cnt));
q.push({0 + f[S], {0, S}});
while (!q.empty()) {
int u = q.top().second.second, d = q.top().second.first;
q.pop();
++cnt[u];
if (cnt[T] == K) return d;
for (int i = head[u]; ~i; i = e[i].nxt_) {
int v = e[i].v_, w = e[i].w_;
if (cnt[v] >= K) continue;
if (f[v] >= 0x3f3f3f3f) continue;
q.push({-(f[v] + d + w), {d + w, v}});
}
}
return -1;
}

int main() {
cin >> n >> m;
int u, v, w;
memset(head, -1, sizeof(head)), memset(rhead, -1, sizeof(rhead));
for (int i = 1; i <= m; ++i) {
cin >> u >> v >> w;
add(head, u, v, w), add(rhead, v, u, w);
}
cin >> S >> T >> K;
dijkstra();
cout << astar() << endl;
return 0;
}

思路

Astar模板题,具体步骤如下:

  1. 先反向建图,以终点T为起点,对反向图做一次dijkstra,每个点对终点的单源最短路记录为f

  2. 视角换回正向图。设当前点为u,当前点到起点S的真实距离为dis[u]

    那么状态为三元组(dis[u] + f[u], dis[u], u)

    第一维很好理解,当前点到起点S的距离+当前点到终点的最短距离

    按照单源最短路的贪心思想,每次取出第一维的最小,最终得到的dis[T]一定是S到T的最短路,因为该条路径的开销相比其他路更小。

    第二维记录当前点到起点S的距离

    第三维记录当前点

    那么初始状态就是(0 + f[S], 0, S),终态就是(dis[T] + 0, dis[T], T),最短路就是dis[T]

    由于题目问的是K短路,所以额外开一个cnt数组记录节点的出队次数就行,第K次出队就是第K短

  3. 实现部分还有几个重要细节:

    1. 由于求的是K短路,所以节点出队K次后就没必要继续入队,因为不可能用得上K+1短路
    2. 如果f[u] >= 0x3f3f3f3f,说明该点到终点不可达,不能塞进队列里
    3. 题目要求每条最短路中至少要包含一条边,所以若终点起点是同一个点,那么求K短路应改为求第K+1短路,因为第1短路没有边(两个点重合距离为0,是最短的)