HDU 6705 - path

题目链接:HDU 6705

题目要在无源有向带圈图中找第\(k\)短的路径。 比赛的时候我用堆动态维护前\(50000\)大的\(d\)(与Dijkstra中\(d\)意义相同),暴力拓边,以TLE告终。 实际上没有必要对所有的边进行拓展,如下图中\(A\)拓展到了\(B\),这条边是全局最小的边,接着我们用\(B\)去拓展新边的时候,其实只要考虑与\(B\)邻接的最小的一条边,这个时候\(AB+BC\)被加入堆中,但是有可能\(AB+BC\)不是\(AB\)的下一个状态,因为只要比\(AB\)大一点点就可能是它的下一个状态,所以\(AF\)可能影响\(AB+BC\)的排名,所以也要加入堆中。

所以,综上所述,我们对每个点的出边进行排序,用堆动态维护当前最小的路径,每次BFS拓展的时候只要拓展当前点最小的出边和当前边的下一条边即可。 渐进时间复杂度\(O(T(n+q)\log n)\)

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX_N = 50000 + 5;
struct Edge {
int from, to; LL cost;
friend bool operator < (const Edge &u, const Edge &v) {
return u.cost < v.cost;
}
};
int T, n, m, q, qr[MAX_N];
LL ans[MAX_N];
vector <Edge> g[MAX_N];
struct Status {
int from, to, id;
LL cost;
friend bool operator < (const Status &u, const Status &v) {
return u.cost > v.cost;
}
};
priority_queue <Status> que;
int main() {
scanf("%d", &T);
for (int cs = 1; cs <= T; ++cs) {
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; ++i) g[i].clear();
int u, v, w;
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
g[u].push_back({u, v, w});
}
while (!que.empty()) que.pop();
for (int i = 1; i <= n; ++i) sort(g[i].begin(), g[i].end());
for (int i = 1; i <= n; ++i) {
if (g[i].size()) que.push({g[i][0].from, g[i][0].to, 0, g[i][0].cost});
}
int maxK = -1;
for (int i = 1; i <= q; ++i) scanf("%d", qr + i), maxK = max(maxK, qr[i]);
for (int i = 1; i <= maxK; ++i) {
Status t = que.top(); que.pop();
ans[i] = t.cost;
if (g[t.to].size()) que.push({g[t.to][0].from, g[t.to][0].to, 0, t.cost + g[t.to][0].cost});
if (t.id + 1 < g[t.from].size())
que.push({
g[t.from][t.id + 1].from,
g[t.from][t.id + 1].to,
t.id + 1,
t.cost - g[t.from][t.id].cost + g[t.from][t.id + 1].cost
});
}
for (int i = 1; i <= q; ++i) printf("%lld\n", ans[qr[i]]);
}
return 0;
}