[GXOI/GZOI2019] 旅行者
[GXOI/GZOI2019] 旅行者
题目描述
J 国有 $n$ 座城市,这些城市之间通过 $m$ 条单向道路相连,已知每条道路的长度。
一次,居住在 J 国的 Rainbow 邀请 Vani 来作客。不过,作为一名资深的旅行者,Vani 只对 J 国的 $k$ 座历史悠久、自然风景独特的城市感兴趣。
为了提升旅行的体验,Vani 想要知道他感兴趣的城市之间「两两最短路」的最小值(即在他感兴趣的城市中,最近的一对的最短距离)。
也许下面的剧情你已经猜到了—— Vani 这几天还要忙着去其他地方游山玩水,就请你帮他解决这个问题吧。
输入格式
每个测试点包含多组数据,第一行是一个整数 $T$,表示数据组数。注意各组数据之间是互相独立的。
对于每组数据,第一行包含三个正整数 $n,m,k$,表示 J 国的 $n$ 座城市(从 $1 \sim n$ 编号),$m$ 条道路,Vani 感兴趣的城市的个数 $k$。
接下来 $m$ 行,每行包括 $3$ 个正整数 $x,y,z$,表示从第 $x$ 号城市到第 $y$ 号城市有一条长度为 $z$ 的单向道路。注意 $x,y$ 可能相等,一对 $x,y$ 也可能重复出现。
接下来一行包括 $k$ 个正整数,表示 Vani 感兴趣的城市的编号。
输出格式
输出文件应包含 $T$ 行,对于每组数据,输出一个整数表示 $k$ 座城市之间两两最短路的最小值。
输入输出样例 #1
输入 #1
2
6 7 3
1 5 3
2 3 5
1 4 3
5 3 2
4 6 5
4 3 7
5 6 4
1 3 6
7 7 4
5 3 10
6 2 7
1 2 6
5 4 2
4 3 4
1 7 3
7 2 4
1 2 5 3
输出 #1
5
6
说明/提示
样例解释
对于第一组数据,$1$ 到 $3$ 最短路为 $5$;$1$ 到 $6$ 最短路为 $7$;$3,6$ 无法到达,所以最近的两点为 $1,3$,最近的距离为 $5$。
对于第二组数据,$1$ 到 $2$ 最短路为 $6$;$5$ 到 $3$ 最短路为 $6$;其余的点均无法互相达,所以最近的两点为 $1,2$ 和 $5,3$,最近的距离为 $6$。
数据范围
$2 \le k \le n$,$1 \le x,y \le n$,$1 \le z \le 2 \times 10^9$,$T \leq 5$。
| 测试点编号 | n 的规模 | m 的规模 | 约定 |
| 1 | $\leq$ 1,000 | $\leq$ 5,000 | 无 |
| 2 | $\leq$ 1,000 | $\leq$ 5,000 | 无 |
| 3 | $\leq$ 100,000 | $\leq$ 500,000 | 保证数据为有向无环图 |
| 4 | $\leq$ 100,000 | $\leq$ 500,000 | 保证数据为有向无环图 |
| 5 | $\leq$ 100,000 | $\leq$ 500,000 | 保证数据为有向无环图 |
| 6 | $\leq$ 100,000 | $\leq$ 500,000 | 无 |
| 7 | $\leq$ 100,000 | $\leq$ 500,000 | 无 |
| 8 | $\leq$ 100,000 | $\leq$ 500,000 | 无 |
| 9 | $\leq$ 100,000 | $\leq$ 500,000 | 无 |
| 10 | $\leq$ 100,000 | $\leq$ 500,000 | 无 |
2024-12-18 管理员注:在测试点五中可能存在自环。
解题思路
最暴力的做法就是对每个感兴趣节点作为源点跑最短路,然后对到其他感兴趣节点的距离取最小值,时间复杂度为 $O(km\log{n})$,超时。
不妨假设在感兴趣节点中具有最短路径的两个端点是 $x$ 和 $y$,最短路径为 $x \to v_1 \to v_2 \to \cdots v_m \to y$($x$ 和 $y$ 可以看作是 $v_0$ 和 $v_{m+1}$)。容易知道 $v_i$ 一定不是感兴趣节点,否则会有更短的路径,矛盾了。此时考虑路径上任意一条边 $(v_i, v_{i+1})$ 的端点 $v_i$ 和 $v_{i+1}$,距离 $v_i$ 最近的感兴趣节点一定是 $x$,距离 $v_{i+1}$ 最近的感兴趣节点一定是 $y$。同样的,如果 $x$ 不是距离 $v_i$ 最近的感兴趣节点,那么一定存在另外一个感兴趣节点 $x'$ 距离 $v_i$ 更短,从而有更短的路径,矛盾。同理分析 $v_{i+1}$。
这启发我们可以先分求出感兴趣节点到每个点 $u$ 的最短距离,记作 $d_1[u]$,同时维护 $c_1[u]$ 表示到达 $u$ 最近的感兴趣节点;以及每个点 $u$ 到感兴趣节点的最短距离,记作 $d_2[u]$,同时维护 $c_2[u]$ 表示 $u$ 到达的最近的感兴趣节点。然后枚举每条边 $(u,v,w)$,如果 $c_1[u] \ne c_2[v]$,那么感兴趣节点 $c_1[u]$ 到 $c_2[v]$ 的最短距离就是 $d_1[u] + w + d_2[v]$。由于在感兴趣节点中最短路径不经过非感兴趣节点,因此通过枚举每条边一定能求得两个感兴趣节点中的最短路径。
剩下的问题是,如何求出感兴趣节点到每个点的最短距离,这个只需将所有感兴趣节点作为源点跑一遍 dijkstra 即可。如何求出每个点到感兴趣节点的最短距离,由于 dijkstra 求的是从(多个)源点出发到单个节点的最短距离,现在需要求的是单个节点到多个节点的最短距离。因此需要对原图建一个反图(即反转每条边的方向),然后在反图上以所有感兴趣节点作为源点跑一遍 dijkstra。
AC 代码如下,时间复杂度为 $O(m \log{n})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, M = 1e6 + 5;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k;
int h1[N], h2[N], e[M], wt[M], ne[M], idx;
int a[N];
LL d1[N], d2[N];
int c1[N], c2[N];
bool vis[N];
void add(int *h, int u, int v, int w) {
e[idx] = v, wt[idx] = w, ne[idx] = h[u], h[u] = idx++;
}
void dijkstra(int *h, LL *d, int *c) {
priority_queue<array<LL, 2>, vector<array<LL, 2>>, greater<array<LL, 2>>> pq;
memset(d, 0x3f, n + 1 << 3);
for (int i = 0; i < k; i++) {
pq.push({0, a[i]});
d[a[i]] = 0;
c[a[i]] = a[i];
}
memset(vis, 0, n + 1);
while (!pq.empty()) {
auto p = pq.top();
pq.pop();
if (vis[p[1]]) continue;
vis[p[1]] = true;
for (int i = h[p[1]]; i != -1; i = ne[i]) {
int v = e[i], w = wt[i];
if (d[v] > p[0] + w) {
d[v] = p[0] + w;
c[v] = c[p[1]];
pq.push({d[v], v});
}
}
}
}
void solve() {
cin >> n >> m >> k;
idx = 0;
memset(h1, -1, sizeof(h1));
memset(h2, -1, sizeof(h2));
while (m--) {
int u, v, w;
cin >> u >> v >> w;
add(h1, u, v, w);
add(h2, v, u, w);
}
for (int i = 0; i < k; i++) {
cin >> a[i];
}
dijkstra(h1, d1, c1);
dijkstra(h2, d2, c2);
LL ret = INF;
for (int i = 1; i <= n; i++) {
for (int j = h1[i]; j != -1; j = ne[j]) {
int u = i, v = e[j], w = wt[j];
if (c1[u] != c2[v]) ret = min(ret, d1[u] + w + d2[v]);
}
}
cout << ret << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
再提供另外一个很难想到的做法。
出发点是对每个感兴趣节点作为源点跑最短路很慢,因此能不能像上面做法那样选择多个感兴趣节点作为源点。我们把感兴趣节点划分成两个集合 $S$ 和 $T$(先不用管怎么划分),然后求这两个集合的最短距离。下面以 $S \to T$ 为例($T \to S$ 同理),以集合 $S$ 中的节点作为源点跑 dijkstra,得到 $d[u]$ 表示从集合 $S$ 中的节点出发到达点 $u$ 的最短距离,那么 $S \to T$ 的最短距离就是 $\min\limits_{u \in T}\{d[u]\}$。
和上面方法一样,不妨假设在感兴趣节点中具有最短路径的两个端点是 $x$ 和 $y$,只要将 $x$ 和 $y$ 分别划分到 $S$ 或 $T$ 两个不同的集合,那么这两个集合的最短距离就是感兴趣节点中的最短距离。因此现在的问题是应该怎么划分。方法还是挺难想到的,就是根据节点编号在二进制下的第 $i \, (0 \leq i \leq \left\lfloor \log{n} \right\rfloor )$ 位进行划分,如果 $u$ 在二进制下的第 $i$ 位是 $0$ 则划分到 $S$,否则划分到 $T$。因为 $x \ne y$,因此一定存在某位 $x$ 和 $y$ 不同,被划分到两个不同的集合。
因此做法就是枚举每个二进制位,把感兴趣节点划分成两个集合,求两个集合的最短距离。
AC 代码如下,时间复杂度为 $O(m \log^2{n})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, M = 5e5 + 5;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int h[N], e[M], wt[M], ne[M], idx;
int a[N];
LL d[N];
bool vis[N];
void add(int u, int v, int w) {
e[idx] = v, wt[idx] = w, ne[idx] = h[u], h[u] = idx++;
}
LL dijkstra(vector<int> &p, vector<int> &q) {
priority_queue<array<LL, 2>, vector<array<LL, 2>>, greater<array<LL, 2>>> pq;
memset(d, 0x3f, sizeof(d));
for (auto &x : p) {
pq.push({0, x});
d[x] = 0;
}
memset(vis, 0, sizeof(vis));
while (!pq.empty()) {
auto p = pq.top();
pq.pop();
if (vis[p[1]]) continue;
vis[p[1]] = true;
for (int i = h[p[1]]; i != -1; i = ne[i]) {
int v = e[i], w = wt[i];
if (d[v] > p[0] + w) {
d[v] = p[0] + w;
pq.push({d[v], v});
}
}
}
return d[*min_element(q.begin(), q.end(), [&](int i, int j) {
return d[i] < d[j];
})];
}
void solve() {
int n, m, k;
cin >> n >> m >> k;
idx = 0;
memset(h, -1, sizeof(h));
while (m--) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
}
for (int i = 0; i < k; i++) {
cin >> a[i];
}
LL ret = INF;
for (int i = 0; 1 << i <= n; i++) {
vector<vector<int>> p(2);
for (int j = 0; j < k; j++) {
p[a[j] >> i & 1].push_back(a[j]);
}
if (p[0].empty() || p[1].empty()) continue;
ret = min(ret, dijkstra(p[0], p[1]));
ret = min(ret, dijkstra(p[1], p[0]));
}
cout << ret << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
参考资料
【题解】P5304 [GXOI/GZOI2019]旅行者(dijkstra,图论,最短路):https://www.cnblogs.com/xrkforces/p/luogu-P5304.html
P5304 [GXOI/GZOI2019]旅行者 - 洛谷专栏:https://www.luogu.com.cn/article/nagswult
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18810430

浙公网安备 33010602011771号