题解:洛谷 P14076 货物运输
【题目来源】
洛谷:P14076 [GESP202509 六级] 货物运输 - 洛谷
【题目描述】
A 国有 \(n\) 座城市,依次以 \(1,2,…,n\) 编号,其中 \(1\) 号城市为首都。这 \(n\) 座城市由 \(n−1\) 条双向道路连接,第 \(i\) 条道路(\(1≤i<n\))连接编号为 \(u_i,v_i\) 的两座城市,道路长度为 \(l_i\)。任意两座城市间均可通过双向道路到达。
现在 A 国需要从首都向各个城市运送货物。具体来说,满载货物的车队会从首都开出,经过一座城市时将对应的货物送出,因此车队需要经过所有城市。A 国希望你设计一条路线,在从首都出发经过所有城市的前提下,最小化经过的道路长度总和。注意一座城市可以经过多次,车队最后可以不返回首都。
【输入】
第一行,一个正整数 \(n\),表示 A 国的城市数量。
接下来 \(n−1\) 行,每行三个正整数 \(u_i,v_i,l_i\),表示一条双向道路连接编号为 \(u_i,v_i\) 的两座城市,道路长度为 \(l_i\)。
【输出】
一行,一个整数,表示你设计的路线所经过的道路长度总和。
【输入样例】
4
1 2 6
1 3 1
3 4 5
【输出样例】
18
【核心思想】
-
问题分析:给定一棵以 \(1\) 号城市为根的树,需要从根出发遍历所有节点,可以重复经过节点和边,最后不必返回根。求经过的道路长度总和的最小值。由于必须访问所有节点,每条边至少需要走一次向下,而为了返回继续访问其他分支,大多数边需要走两次(下去再上来)。最后停留的那条路径上的边可以少走一次返回。因此,总路程 = \(2 \times \text{总边权} - \text{从根到终点的路径长度}\)。为了使总路程最小,应选择距离根最远的节点作为终点,即最大化从根出发的路径长度。
-
算法选择:
- DFS 遍历树:从根节点 \(1\) 开始深度优先搜索,计算从根出发到任意节点的最长路径长度
maxd。 - 累加边权:在读入边时累加所有边的权值得到
sum。 - 最终答案:
2 * sum - maxd。
- DFS 遍历树:从根节点 \(1\) 开始深度优先搜索,计算从根出发到任意节点的最长路径长度
-
关键步骤:
- 读入 \(n\),建立邻接表存储无向树。
- 读入 \(n-1\) 条边,累加边权到
sum。 - 从节点 \(1\) 开始 DFS,传入当前节点 \(u\) 和父节点 \(fa\):
- 初始化
maxs = 0表示从 \(u\) 向下走的最长路径。 - 遍历所有邻接点 \(v\)(跳过 \(fa\)),递归计算
dfs(v, u) + w,更新maxs。 - 更新全局最长路径
maxd = max(maxd, maxs)。 - 返回
maxs。
- 初始化
- 输出
2 * sum - maxd。
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),每个节点和边访问一次。
- 空间复杂度:\(O(n)\),邻接表存储树。
-
DFS 遍历树的核心思想:
- 遍历所有节点的最小代价:在树上遍历所有节点并返回起点,每条边必须走两次,总代价为 \(2 \times \text{总边权}\)。
- 允许不返回起点:最后停留的路径可以省去返回的重复走,因此只需减去从起点到终点的路径长度。
- 最远终点选择:为了使总代价最小,应选择距离起点最远的节点作为终点,即最大化减去的路径长度。因此问题转化为求从根出发的最长路径。
- 适用于树上遍历、邮差问题等经典场景。
【算法标签】
普及- #DFS-树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用长整型防止溢出
const int N = 100005; // 定义最大节点数
typedef pair<int, int> PII; // 定义边类型,存储邻接节点和边权
int n; // 树的节点数
int sum = 0; // 存储所有边的总权值
int ans = 0; // 存储树的直径(最长路径长度)
vector<PII> ve[N]; // 邻接表存储树结构
/**
* 深度优先搜索计算从节点u出发的最长路径
* @param u 当前节点
* @param fa 父节点(防止回退)
* @return 从u出发的最长路径长度
*/
int dfs(int u, int fa)
{
int maxs = 0; // 存储从u出发的最长路径长度
// 遍历所有邻接节点
for (int i = 0; i < ve[u].size(); i++)
{
int v = ve[u][i].first; // 邻接节点
int l = ve[u][i].second; // 边权
// 跳过父节点
if (v == fa)
continue;
// 递归计算子节点的最长路径,并更新当前最大值
maxs = max(maxs, dfs(v, u) + l);
}
// 更新全局最长路径(树的直径)
ans = max(maxs, ans);
return maxs;
}
signed main()
{
// 输入节点数
cin >> n;
// 输入树的结构(n-1条边)
for (int i = 1; i < n; i++)
{
int u, v, l;
cin >> u >> v >> l;
// 添加到邻接表(无向图)
ve[u].push_back({v, l});
ve[v].push_back({u, l});
// 累加所有边的权值
sum += l;
}
// 计算树的直径(最长路径)
int t = dfs(1, -1);
/**
* 输出结果:
* 所有边权之和的两倍减去最长路径长度
* 这是解决"邮差问题"(遍历所有边的最短路径)的经典公式
*/
cout << sum * 2 - t << endl;
return 0;
}
【运行结果】
4
1 2 6
1 3 1
3 4 5
18
浙公网安备 33010602011771号