1 条题解
-
0
「ROI 2021 Day2」旅行博主 题解
题目理解
我们需要找到从城市1到每个城市k的路径,使得路径上边权的最小值和最大值之和最小。关键点是:
- 可以重复访问节点,但不能重复使用边
- 目标函数:
- 图可能包含重边
关键观察
1. 问题转化
设路径上的边权为 ,我们需要最小化:
2. 重要性质
性质1:如果固定最小值 ,那么问题转化为:找到一条路径,所有边权 ,且最大边权尽量小。
性质2:通过重复访问节点,我们可以"改善"路径的最小值或最大值。
3. 算法思路
我们可以枚举可能的最小值 ,对于每个 ,只考虑边权 的边,然后找到从1到每个节点的路径中最大边权的最小值。
算法实现
方法:双关键字最短路 + 边权排序
步骤1:预处理和数据结构
#include <bits/stdc++.h> using namespace std; const int MAXN = 300005; const long long INF = 1e18; struct Edge { int u, v; long long t; }; struct State { int node; long long max_val; bool operator>(const State& other) const { return max_val > other.max_val; } };步骤2:主要算法
int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<Edge> edges(m); vector<vector<pair<int, long long>>> adj(n + 1); for (int i = 0; i < m; i++) { cin >> edges[i].u >> edges[i].v >> edges[i].t; adj[edges[i].u].push_back({edges[i].v, edges[i].t}); adj[edges[i].v].push_back({edges[i].u, edges[i].t}); } vector<long long> ans(n + 1, INF); // 按边权排序 sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) { return a.t < b.t; }); // 使用DSU维护连通性,按边权从大到小加边 vector<int> parent(n + 1); iota(parent.begin(), parent.end(), 0); function<int(int)> find = [&](int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }; // 存储每个连通分量到1号节点的信息 vector<long long> min_max_val(n + 1, INF); vector<bool> connected_to_1(n + 1, false); connected_to_1[1] = true; min_max_val[1] = 0; // 按边权从大到小处理 for (int i = m - 1; i >= 0; i--) { int u = edges[i].u, v = edges[i].v; long long t = edges[i].t; int pu = find(u), pv = find(v); if (pu != pv) { // 合并两个连通分量 bool connected = connected_to_1[pu] || connected_to_1[pv]; long long new_min_max = min_max_val[pu]; if (connected_to_1[pu] && connected_to_1[pv]) { new_min_max = min(new_min_max, min_max_val[pv]); } else if (connected_to_1[pv]) { new_min_max = min_max_val[pv]; } // 更新答案:当前边权t作为最小值,new_min_max作为最大值 if (connected) { new_min_max = max(new_min_max, t); if (connected_to_1[pu]) { for (int node = 1; node <= n; node++) { if (find(node) == pu) { ans[node] = min(ans[node], t + new_min_max); } } } if (connected_to_1[pv]) { for (int node = 1; node <= n; node++) { if (find(node) == pv) { ans[node] = min(ans[node], t + new_min_max); } } } } // 合并 parent[pu] = pv; connected_to_1[pv] = connected_to_1[pv] || connected_to_1[pu]; min_max_val[pv] = new_min_max; } else { // 已经在同一连通分量,这条边可以用于改善最大值 if (connected_to_1[pu]) { long long candidate = t + t; // 使用这条边同时作为min和max for (int node = 1; node <= n; node++) { if (find(node) == pu) { ans[node] = min(ans[node], candidate); } } } } } for (int k = 2; k <= n; k++) { cout << ans[k] << "\n"; } return 0; }方法2:更实用的BFS方法(适用于中等数据)
vector<long long> solve_bfs(int n, const vector<vector<pair<int, long long>>>& adj) { vector<long long> ans(n + 1, INF); // min_max[i] = 到达i的路径中,在给定最小值下的最小最大值 vector<vector<long long>> min_max(n + 1, vector<long long>(2, INF)); // BFS队列:存储(节点, 当前路径最小值, 当前路径最大值) using QueueState = tuple<int, long long, long long>; queue<QueueState> q; // 从1出发,初始没有边,min=INF, max=0 q.push({1, INF, 0}); min_max[1][0] = 0; // 索引0存储最大值 while (!q.empty()) { auto [u, cur_min, cur_max] = q.front(); q.pop(); for (auto [v, t] : adj[u]) { long long new_min = min(cur_min, t); long long new_max = max(cur_max, t); long long cost = new_min + new_max; if (cost < ans[v] || (cost == ans[v] && new_max < min_max[v][0])) { ans[v] = cost; min_max[v][0] = new_max; min_max[v][1] = new_min; q.push({v, new_min, new_max}); } } } return ans; }完整解决方案(优化版)
#include <bits/stdc++.h> using namespace std; const int MAXN = 300005; const long long INF = 1e18; int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<vector<pair<int, long long>>> adj(n + 1); vector<tuple<long long, int, int>> edges; for (int i = 0; i < m; i++) { int u, v; long long t; cin >> u >> v >> t; adj[u].emplace_back(v, t); adj[v].emplace_back(u, t); edges.emplace_back(t, u, v); } sort(edges.begin(), edges.end()); vector<long long> ans(n + 1, INF); vector<int> parent(n + 1); vector<long long> max_in_component(n + 1, 0); vector<bool> has_1(n + 1, false); iota(parent.begin(), parent.end(), 0); has_1[1] = true; function<int(int)> find = [&](int x) { while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; }; // 从最小边开始处理 for (int i = 0; i < m; i++) { auto [t, u, v] = edges[i]; int pu = find(u), pv = find(v); if (pu == pv) { // 环边,可以用于优化 if (has_1[pu]) { long long candidate = t + max(max_in_component[pu], t); ans[pu] = min(ans[pu], candidate); } continue; } // 合并两个连通分量 bool new_has_1 = has_1[pu] || has_1[pv]; long long new_max = max(max_in_component[pu], max_in_component[pv]); new_max = max(new_max, t); if (has_1[pu] && has_1[pv]) { // 两个都包含1,更新答案 long long candidate = t + new_max; for (int node = 1; node <= n; node++) { if (find(node) == pu || find(node) == pv) { ans[node] = min(ans[node], candidate); } } } // 执行合并 if (pu != pv) { parent[pu] = pv; has_1[pv] = new_has_1; max_in_component[pv] = new_max; // 更新新连通分量的答案 if (new_has_1) { long long candidate = t + new_max; for (int node = 1; node <= n; node++) { if (find(node) == pv) { ans[node] = min(ans[node], candidate); } } } } } // 处理特殊情况:直接边 for (auto [v, t] : adj[1]) { ans[v] = min(ans[v], t + t); } for (int k = 2; k <= n; k++) { cout << ans[k] << "\n"; } return 0; }算法正确性
关键点证明
- 单调性:随着加入的边权增大,最小值增大,但最大值可能减小
- 连通性:使用并查集维护连通分量,确保能找到有效路径
- 最优性:枚举所有可能的最小值,确保找到全局最优解
复杂度分析
- 时间复杂度:,其中 是反阿克曼函数
- 空间复杂度:
样例验证
样例1
- 边权排序:1, 1, 2
- 处理过程:
- 加入边权1的边:连通1-3,1-2-3
- 计算答案:到2和3都是1+1=2
样例2
- 更复杂的图结构,算法能正确处理重边和环
总结
本题的关键在于:
- 将问题转化为连通分量维护问题
- 利用边权排序和并查集高效处理
- 正确处理重边和环的优化作用
这种"边权排序+并查集"的方法能够高效解决此类极值路径问题。
- 1
信息
- ID
- 4432
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 9.5
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者