「GXOI / GZOI2019」旅行者
这题给出两种方法,只给出前一种方法的代码,因为后一种代码可能跑不过(后面会讲)
方法1
我们不难发现最优情况下的路径,一定只在起点和终点两个位置是关键点,中间不可能有别的关键点,不然就可以只取一半,就会更优。
那么我们就可以建正反两张图,跑两次多源 \(\text{Dijkstra}\),记 \(f_u\) 为从关键点走到 \(u\) 的最短路,\(rf_u\) 记录是哪个点,\(g_u\) 表示从 \(u\) 点走到某一个关键点的最短路,\(rg_u\) 记录是哪个点。
那么我们枚举每一条边 \((u, v, w)\),如果 \(rf_u \ne rg_v\) ,就可以用 \(f_u + w + g_v\) 更新答案,如果 \(rf_u = rg_v\),可以证明这样一定不会是最优解。
方法2
新建超级源汇点 \(s, t\),把所有关键点分为 \(A, B\) 两个点集,钦定用一条路径 \(s \to a \to b \to t\) 或 \(s \to b \to a \to t\) 更新答案,其中 \(a \in A, b \in B\)。
考虑怎么分组以覆盖所有的组合情况,我们发现最优解中的 \(a, b\) 一定是两个不同的点,那么我们把这些点按照其二进制表示的某一位的值来分组,总共分 \(\log\) 次,不难发现这样一定可以覆盖所有可能的解。
分完组之后就直接把 \(s\) 向 \(A\) 中的点连边权为 \(0\) 的边,\(B\) 中的点向 \(t\) 连边权为 \(0\) 的边,跑一边最短路就可以求出当前分组下 \(s \to a \to b \to t\) 类型的最优解,另一种类型同理。
这样复杂度是 \(n \log^2 n\) 的,理论上来说可以过,但是由于每次都要删边连边,所以操作不当就可能会 \(\text{TLE}\) ,这也是为什么不用这种方法的原因(((
方法1参考代码:
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
template < class T > void read(T& s) {
s = 0; int f = 0; char c = getchar();
while ('0' > c || c > '9') f |= c == '-', c = getchar();
while ('0' <= c && c <= '9') s = s * 10 + c - 48, c = getchar();
s = f ? -s : s;
}
typedef long long LL;
const int _ = 1e5 + 5, __ = 5e5 + 5;
int tot, head[_]; struct Edge { int v, w, nxt; } edge[__];
void Add_edge(int u, int v, int w) { edge[++tot] = (Edge) { v, w, head[u] }, head[u] = tot; }
int n, m, k, a[_], vis[_], r[2][_]; LL dis[2][_];
struct node { int u, v, w; } o[__];
priority_queue < pair < LL, int > > Q;
void Dijkstra(int x) {
memset(dis[x], 0x3f, sizeof dis[x]);
memset(r[x], 0, sizeof r[x]);
memset(vis, 0, sizeof vis);
for (int i = 1; i <= k; ++i)
dis[x][a[i]] = 0, r[x][a[i]] = a[i], Q.push(make_pair(0, a[i]));
while (!Q.empty()) {
int u = Q.top().second; Q.pop();
if (vis[u]) continue ; vis[u] = 1;
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].v, w = edge[i].w;
if (dis[x][v] > dis[x][u] + w)
dis[x][v] = dis[x][u] + w, r[x][v] = r[x][u], Q.push(make_pair(-dis[x][v], v));
}
}
}
void solve() {
read(n), read(m), read(k);
for (int u, v, w, i = 1; i <= m; ++i)
read(u), read(v), read(w), o[i] = (node) { u, v, w };
for (int i = 1; i <= k; ++i) read(a[i]);
memset(head, tot = 0, sizeof head);
for (int i = 1; i <= m; ++i) Add_edge(o[i].u, o[i].v, o[i].w);
Dijkstra(0);
memset(head, tot = 0, sizeof head);
for (int i = 1; i <= m; ++i) Add_edge(o[i].v, o[i].u, o[i].w);
Dijkstra(1);
LL ans = 1e18;
for (int i = 1; i <= m; ++i) {
int u = o[i].u, v = o[i].v, w = o[i].w;
if (r[0][u] && r[1][v] && r[0][u] != r[1][v] && u != v)
ans = min(ans, dis[0][u] + w + dis[1][v]);
}
printf("%lld\n", ans);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("cpp.in", "r", stdin), freopen("cpp.out", "w", stdout);
#endif
int T; read(T);
while (T--) solve();
return 0;
}