题解:洛谷 AT_abc463_e Roads and Gates
【题目来源】
洛谷:AT_abc463_e [ABC463E] Roads and Gates - 洛谷
【题目描述】
The country of AtCoder has \(N\) cities and \(M\) roads. The \(i\)-th road \((1\le i\le M)\) connects cities \(u _ i\) and \(v _ i\) bidirectionally, and allows travel from one to the other in \(T _ i\) minutes.
Additionally, each city has a warp gate installed, and you can travel from city \(i\ (1\le i\le N)\) to city \(j\ (1\le j\le N)\) using the warp gate in \(X _ i+X _ j+Y\) minutes.
There are no other ways to travel between cities in the country.
For each \(k=2,3,\ldots,N\), solve the following problem:
- Find the minimum time required to travel from city \(1\) to city \(k\).
The time required to transfer from a road or warp gate to another road or warp gate at the same city is negligible.
AtCoder 国有 \(N\) 个城市和 \(M\) 条道路。第 \(i\) 条道路 \((1\le i\le M)\) 双向连接城市 \(u_i\) 和城市 \(v_i\),从一端到另一端需要 \(T_i\) 分钟。
此外,每个城市都安装了一个传送门,你可以使用传送门从城市 \(i\ (1\le i\le N)\) 前往城市 \(j\ (1\le j\le N)\),所需时间为 \(X_i+X_j+Y\) 分钟。
该国城市之间没有其他通行方式。
对于每个 \(k=2,3,\ldots,N\),请解决以下问题:
- 求从城市 \(1\) 到城市 \(k\) 所需的最短时间。
在同一城市从道路换乘到传送门,或从传送门换乘到道路所需的时间可以忽略不计。
【输入】
The input is given from Standard Input in the following format:
\(N\) \(M\) \(Y\)
\(u _ 1\) \(v _ 1\) \(T _ 1\)
\(u _ 2\) \(v _ 2\) \(T _ 2\)
\(\vdots\)
\(u _ M\) \(v _ M\) \(T _ M\)
\(X _ 1\) \(X _ 2\) \(\ldots\) \(X _ N\)
【输出】
Output the answers to the problems for \(k=2,3,\ldots,N\) in this order, separated by spaces.
【输入样例】
7 7 3
1 2 1
1 3 6
2 3 4
3 5 8
3 7 4
4 5 2
4 7 9
3 1 4 1 5 9 2
【输出样例】
1 5 6 8 14 7
【核心思想】
-
问题分析:给定 \(N\) 个城市和 \(M\) 条双向道路,每条道路有通行时间 \(T_i\)。此外,每个城市都有传送门,从城市 \(i\) 到城市 \(j\) 的传送时间为 \(X_i + X_j + Y\)。求从城市 \(1\) 到每个城市 \(k\ (k=2,3,\ldots,N)\) 的最短时间。这是一个单源最短路 + 虚拟节点技巧问题。
-
算法选择:
- Dijkstra 算法:所有边权均为正,使用 Dijkstra 求单源最短路
- 虚拟节点技巧(Super Node):将传送门的两两之间的完全图转化为通过虚拟节点的普通边,避免建 \(N^2\) 条边
- 链式前向星:高效存储稀疏图的邻接表结构
-
关键步骤:
- 初始化:读取 \(N\)(城市数)、\(M\)(道路数)、\(Y\)(传送门基础时间)
- 存储道路:读取 \(M\) 条双向道路 \((u_i, v_i, T_i)\),使用链式前向星添加双向边
- 传送门边转化:
- 引入虚拟节点 \(0\)
- 添加边 \(0 \to i\),权值为 \(X_i + Y\)(接收传送的部分代价)
- 添加边 \(i \to 0\),权值为 \(X_i\)(发起传送的部分代价)
- 从城市 \(i\) 传送到城市 \(j\) 的路径为 \(i \to 0 \to j\),总权值 \(X_i + (X_j + Y) = X_i + X_j + Y\)
- 跑 Dijkstra:从城市 \(1\) 出发,使用优先队列(小根堆)维护待处理节点
- 初始化
dist[1] = 0,其余为 \(\infty\) - 每次取出距离最小的未处理节点 \(u\)
- 遍历 \(u\) 的所有邻接边,进行松弛操作:
dist[v] = min(dist[v], dist[u] + w)
- 初始化
- 输出答案:输出
dist[2], dist[3], ..., dist[N]
-
时间/空间复杂度:
- 时间复杂度:\(O((M + N) \log N)\),Dijkstra 使用优先队列的时间复杂度为 \(O(E \log V)\),其中 \(E = 2M + 2N\)(道路双向边 + 传送门边)
- 空间复杂度:\(O(M + N)\),链式前向星存储 \(2M + 2N\) 条边
-
虚拟节点技巧的核心思想:
- 完全图降维:传送门连接所有城市对,直接建边需要 \(O(N^2)\) 条边。通过引入虚拟节点,将 \(N^2\) 条边降为 \(O(N)\) 条边
- 代价拆分:将传送时间 \(X_i + X_j + Y\) 拆分为两部分,分别从城市 \(i\) 到虚拟节点(\(X_i\))和从虚拟节点到城市 \(j\)(\(X_j + Y\)),保证路径总代价不变
- 统一处理:传送门和道路都转化为普通边,无需特殊处理,可直接使用标准 Dijkstra
- 适用场景:完全图或稠密图中的特殊边权结构(如 \(w(i,j) = a_i + b_j + c\)),可通过虚拟节点将边数从 \(O(N^2)\) 降至 \(O(N)\)
【算法标签】
普及 #Dijkstra
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 将 int 定义为 long long,防止距离溢出
const int N = 200005, M = N * 4, INF = 1e18; // N: 最大城市数, M: 最大边数, INF: 无穷大
typedef pair<int, int> PII; // 优先队列元素类型:first=距离, second=节点编号
int n, m, y; // n: 城市数, m: 道路数, y: 传送门基础时间
int h[N], e[M], w[M], ne[M], idx; // 链式前向星:h表头, e终点, w边权, ne下一条, idx当前边数
int x[N], dist[N]; // x[i]: 城市i的传送门参数, dist[i]: 从源点到城市i的最短距离
bool st[N]; // st[i]: 标记城市i是否已确定最短距离
priority_queue<PII, vector<PII>, greater<PII> > heap; // 小根堆,用于Dijkstra
// 链式前向星:添加一条从 a 到 b、权值为 c 的有向边
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// Dijkstra算法:求从源点 u 到所有其他节点的最短距离
void dijkstra(int u)
{
// 初始化距离数组为无穷大
for (int i = 0; i <= n; i++)
{
dist[i] = INF;
}
memset(st, 0, sizeof(st)); // 清空访问标记
while (!heap.empty()) heap.pop(); // 清空优先队列
dist[u] = 0; // 源点到自身的距离为0
heap.push({0, u}); // 将源点入堆
while (!heap.empty())
{
auto t = heap.top(); heap.pop(); // 取出距离最小的节点
int u = t.second, distance = t.first; // u: 节点编号, distance: 当前距离
if (st[u] == true) continue; // 若已处理过,跳过
st[u] = true; // 标记为已确定最短距离
// 遍历 u 的所有邻接边
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i]; // 邻接节点
if (dist[v] > distance + w[i]) // 松弛操作:若找到更短路径
{
dist[v] = distance + w[i]; // 更新最短距离
heap.push({dist[v], v}); // 将更新后的节点入堆
}
}
}
}
signed main() // 使用 signed 替代 int,因为 #define int long long
{
cin >> n >> m >> y; // 读入城市数、道路数、传送门基础时间
memset(h, -1, sizeof(h)); // 初始化链式前向星表头为-1
// 读入 M 条双向道路
while (m--)
{
int u, v, t;
cin >> u >> v >> t;
add(u, v, t), add(v, u, t); // 双向边,添加两条有向边
}
// 读入每个城市的传送门参数 X_i
for (int i = 1; i <= n; i++)
cin >> x[i];
// 添加传送门边:引入虚拟节点0来统一处理传送门
// 从虚拟节点0到城市i的边权为 x[i] + y(相当于从某城市传送到i的 x_j + x_i + y 中的 x_i + y 部分)
// 从城市i到虚拟节点0的边权为 x[i](相当于从i传送到某城市的 x_i + x_j + y 中的 x_i 部分)
for (int i = 1; i <= n; i++)
{
add(0, i, x[i] + y); // 虚拟节点0 -> 城市i,权值 x[i] + y
add(i, 0, x[i]); // 城市i -> 虚拟节点0,权值 x[i]
}
dijkstra(1); // 从城市1出发跑Dijkstra
// 输出从城市1到城市2~N的最短距离
for (int i = 2; i <= n; i++)
cout << dist[i] << " ";
cout << endl;
return 0;
}
【运行结果】
7 7 3
1 2 1
1 3 6
2 3 4
3 5 8
3 7 4
4 5 2
4 7 9
3 1 4 1 5 9 2
1 5 6 8 14 7
浙公网安备 33010602011771号