P14076 [GESP202509 六级] 货物运输

记录131

#include<bits/stdc++.h>
using namespace std;
#define ll long long//定义长整型别名ll,因为边权和最大可达1e14,必须用long long
const int N=1e5+10;//定义常量N,表示城市数量的最大范围
vector<pair<int,ll>>g[N];//定义邻接表g,g[u]存储与城市u相连的{相邻城市, 道路长度}
ll max_dist=0;//定义变量max_dist,用来记录首都到最远城市的距离
//定义深度优先搜索函数,u是当前城市,fa是父节点(防止走回头路),d是当前走过的距离
void dfs(int u,int fa,ll d){
	max_dist=max(max_dist,d);//每次到达一个节点,都尝试更新最远距离
	for(auto edge:g[u]){//遍历当前城市u的所有相邻道路
		int v=edge.first;//相邻的城市编号
		ll w=edge.second;//这条道路的长度
		if(v!=fa){//如果相邻城市不是父节点(即不是回头路)
			dfs(v,u,d+w);//继续向下深搜,距离累加当前道路长度
		}
	}
}
int main(){//主函数入口
	ios::sync_with_stdio(false);//关闭标准流同步,提升输入输出效率
	cin.tie(0);//解除cin与cout的绑定,进一步加快读取速度
	int n;//变量n,表示城市的总数
	ll sum=0;//定义变量sum,用来累加所有道路的长度总和
	cin>>n;//读入城市的数量n
	for(int i=1;i<n;i++){//循环读入n-1条道路的信息
		int u,v;//定义临时变量u和v,代表一条道路连接的两个城市
		ll len;//定义临时变量len,代表这条道路的长度
		cin>>u>>v>>len;//读入这条道路连接的两个城市编号以及长度
		g[u].push_back({v,len});//在邻接表中添加u到v的连接
		g[v].push_back({u,len});//在邻接表中添加v到u的连接(因为是无向图)
		sum+=len;//累加所有道路的长度
	}
	dfs(1,0,0);//从首都(1号城市)开始进行DFS,父节点设为0,初始距离为0
	cout<<2*sum-max_dist;//输出最短总路程:所有边走两倍的代价减去最远的一条回程
	return 0;//程序结束
}

题目传送门https://www.luogu.com.cn/problem/P14076


前言

我是一名专注信奥赛(CSP-J/S、NOIP)的教练。

  • 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
  • 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

核心解题思路

这道题是一道经典的树上路径贪心问题。我们需要从首都出发,遍历所有节点,并且要求总路径最短。

  1. 模型转化
    题目中的 n 座城市和 n-1 条双向道路,且任意两座城市均可到达,这在图论中被称为树(Tree)。首都 1 号节点就是这棵树的根节点。

  2. 核心贪心策略
    在树上遍历所有节点,最朴素的做法是“深度优先搜索(DFS)”:从根节点出发,走到每一个叶子节点,然后原路返回。在这个过程中,每一条边都会被经过两次(去一次,回一次)。因此,所有边的长度总和乘以 2,就是遍历所有节点并回到起点的总代价。
    但是,题目有一个关键条件:“车队最后可以不返回首都”。这意味着,我们可以省去从“最远的那个城市”返回首都的那段路程。为了让总路程最小,我们省去的这段路程必须尽可能长。

  3. 数学公式
    最终的答案 = 所有边的长度总和 × 2 - 从首都出发能到达的最远距离


代码分块详细解释

1. 头文件、常量与全局变量定义

#include<bits/stdc++.h>
using namespace std;
#define ll long long // 定义长整型别名 ll,因为边权和最大可达 1e14,必须用 long long
const int N=1e5+10; // 定义常量 N,表示城市数量的最大范围
vector<pair<int,ll>> g[N]; // 定义邻接表 g,g[u] 存储与城市 u 相连的 {相邻城市, 道路长度}
ll max_dist=0; // 定义变量 max_dist,用来记录首都到最远城市的距离
  • 详细分析:这部分搭建了图论问题的基础数据结构。由于题目中边的长度 l_i 最大可达 109,且最多有 10^5 条边,总路程可能达到 10^14 级别,远远超出 int 的范围,因此必须使用 long longvector<pair<int,ll>> g[N] 是存储无向图(树)的标准邻接表写法,pair 的第一个元素存相邻节点,第二个元素存边权。

2. 深度优先搜索(DFS)求最远距离

// 定义深度优先搜索函数,u 是当前城市,fa 是父节点(防止走回头路),d 是当前走过的距离
void dfs(int u, int fa, ll d){
    max_dist = max(max_dist, d); // 每次到达一个节点,都尝试更新最远距离
    for(auto edge : g[u]){ // 遍历当前城市 u 的所有相邻道路
        int v = edge.first; // 相邻的城市编号
        ll w = edge.second; // 这条道路的长度
        if(v != fa){ // 如果相邻城市不是父节点(即不是回头路)
            dfs(v, u, d + w); // 继续向下深搜,距离累加当前道路长度
        }
    }
}
  • 详细分析:这个 DFS 函数的作用是求树的“加权直径”(从根节点出发的最长路径)。
    • 防止死循环:在树中遍历必须传递父节点 fa,通过 if(v != fa) 确保搜索只向子节点延伸,不会在两个节点之间来回横跳。
    • 距离累加d + w 实时维护从首都(根节点)到当前节点 v 的总路径长度。
    • 全局更新:每当 DFS 走到一个节点,就将其距离与全局变量 max_dist 进行比较,最终 max_dist 就会保存从首都出发能到达的最远城市的距离。

3. 主函数:建图与边权累加

int main(){ // 主函数入口
    ios::sync_with_stdio(false); // 关闭标准流同步,提升输入输出效率
    cin.tie(0); // 解除 cin 与 cout 的绑定,进一步加快读取速度
    int n; // 变量 n,表示城市的总数
    ll sum = 0; // 定义变量 sum,用来累加所有道路的长度总和
    cin >> n; // 读入城市的数量 n
    for(int i = 1; i < n; i++){ // 循环读入 n-1 条道路的信息
        int u, v; // 定义临时变量 u 和 v,代表一条道路连接的两个城市
        ll len; // 定义临时变量 len,代表这条道路的长度
        cin >> u >> v >> len; // 读入这条道路连接的两个城市编号以及长度
        g[u].push_back({v, len}); // 在邻接表中添加 u 到 v 的连接
        g[v].push_back({u, len}); // 在邻接表中添加 v 到 u 的连接(因为是无向图)
        sum += len; // 累加所有道路的长度
    }
  • 详细分析:主函数的前半部分负责数据的读取和图的构建。sum += len 这一步非常关键,它直接计算出了整棵树所有边的权值之和。由于是无向图,添加边时需要双向添加(u->v 和 v->u 都要加),保证 DFS 时能正确遍历到所有相邻节点。

4. 核心计算与输出

    dfs(1, 0, 0); // 从首都(1号城市)开始进行 DFS,父节点设为 0,初始距离为 0
    cout << 2 * sum - max_dist; // 输出最短总路程:所有边走两倍的代价减去最远的一条回程
    return 0; // 程序结束
}
  • 详细分析:这是整个算法的收尾。调用 dfs(1, 0, 0) 从根节点开始遍历,求出最远回程距离 max_dist。最后,利用贪心公式 2 * sum - max_dist 直接得出答案。例如在样例 1 中,总边权和为 6+1+5=12,两倍是 24;从首都 1 出发最远能走到 4 号城市(距离为 1+5=6),所以最终答案是 24 - 6 = 18。

 核心逻辑总结表

代码模块核心变量/操作精炼作用解决的痛点
数据类型#define ll long long使用 64 位长整型防止 105 条边、每条边 109 权值累加时发生数据溢出
图存储vector<pair<int,ll>> g[N]邻接表存无向图完美适配 n ≤ 105 的稀疏图,比邻接矩阵更节省空间
DFS 防回头if(v != fa)记录父节点并剪枝避免在树的双向边中陷入无限递归死循环
贪心公式2 * sum - max_dist核心数学推导将复杂的“遍历所有节点的最短路径”问题,转化为简单的“总边权*2 - 最长单程”
IO 优化ios::sync_with_stdio(false)提升输入输出效率解决 105 级别数据量下 cin/cout 可能导致的超时问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值