|
|
一、 题目大意
给定一棵无根树(由 $N$ 个房间和 $N-1$ 条带权通道组成),以及三个道具位置 $A$、$B$、$C$。目标是找到一个汇合房间 $X$,使得 $A$、$B$、$C$ 到 $X$ 的路径长度之和最小。若有多个 $X$,输出房间编号字典序最小者(即编号最小)。 5 3 1 4 3 5 5 4 3 9 4 1 7 1 2 1 - 参数解析:$N=5$(5 个房间),$A=3$(某样道具在房间 3),$B=1$(某样道具在房间 1),$C=4$(某样道具在房间 4)。 - 树结构:边表示房间间通道及长度,形成树: - 房间 1 和 2 连接,长度 1。 - 房间 1 和 4 连接,长度 7。 - 房间 3 和 4 连接,长度 9。 - 房间 3 和 5 连接,长度 5。 - 等效路径:房间2 —(1)— 房间1 —(7)— 房间4 —(9)— 房间3 —(5)— 房间5。
距离计算: - 到 $A=3$ 的距离: - 房间 3: 0 - 房间 4: 9(直连边) - 房间 1: 9 + 7 = 16 - 房间 2: 16 + 1 = 17 - 房间 5: 5(直连边) - 到 $B=1$ 的距离: - 房间 1: 0 - 房间 2: 1 - 房间 4: 7 - 房间 3: 7 + 9 = 16 - 房间 5: 16 + 5 = 21 - 到 $C=4$ 的距离: - 房间 4: 0 - 房间 1: 7 - 房间 3: 9 - 房间 2: 7 + 1 = 8 - 房间 5: 9 + 5 = 14 |room i| dis[i][A] | dis[i][B] | dis[i][C] |SUM_dis | |------|-----------|-----------|-----------|--------| | 1 | 16 | 0 | 7 | 23 | | 2 | 17 | 1 | 8 | 26 | | 3 | 0 | 16 | 9 | 25 | | 4 | 9 | 7 | 0 | 16 | | 5 | 5 | 21 | 14 | 40 | 输出分析:最小距离和为 $16$,在房间 $4$ 汇合(无其他房间相同和)。因此输出房间编号 $4$ 和距离和 $16$,符合样例输出。 二、 思路解析第一层:解题基石
邻接表存树 + BFS - 树的性质利用:由于树中任意两点间路径唯一且无环,从单点出发到所有其他点的最短路径可通过一次 $BFS$ 或 $DFS$ 计算(边权为正,无需 $Dijkstra$)。 - 关键观察:最小距离和的点一定在 $A$、$B$、$C$ 三点形成的子树中,但为简化实现,可直接计算所有房间到 $A$、$B$、$C$ 的距离(时间复杂度 $O(N)$,$N \leq 20,000$ 可接受)。
第二层:思维脉络
1. 从 $A$ 点执行 $BFS/DFS$,计算每个房间到 $A$ 的距离,存储数组 $distA$。 2. 同理,从 $B$ 点计算 $distB$,从 $C$ 点计算 $distC$。 3. 遍历所有房间 $i$(从 $1$ 到 $N$),计算总距离和 $S_i = \text{distA}[i] + \text{distB}[i] + \text{distC}[i]$。 4. 找到 $S_i$ 最小的房间,若有多个,选 $i$ 最小者。
第三层:难点与陷阱
距离和访问标记数组的初始化和更新、$BFS$ 多次调用前的环境参数初始化处理是易错点; 起初想建图,跑最短路算法,后发现是树形结构,房间之间路径唯一,且通过遍历可以在 $O(N)$ 时间内即可求得到其他所有点的距离,故选 $BFS$。 三、 代码实现
#include<bits/stdc++.h>
using namespace std;
const int N = ___; //房间数上限
const int MX = ___; //无穷大边界
//可以用struct或pair定义边
vector<___> graph[___]; // 邻接表存储树:边{v, w} 存入graph[u]
int disA[___], disB[___], disC[___]; // 分别存储到A、B、C的距离
int n, a, b, c;
int visited[___]; //访问标记数组
// BFS计算单源最短路径(树是无环的,BFS足够)
void bfs(int start, int dis[]) {//由于多次调用BFS,故把disA[],...B[],...C[]直接传给函数中的dis[]
//Bfs函数中数组的传递方式,其核心是传递数组的首地址(即指针),这使得在函数内部可以直接修改主函数中数组的值。
memset(___, ___, ___); //初始化 dis[]
memset(___, ___, ___); //初始化 visited[]
dis[start] = ___; //起点距离初始化
___ //定义队列q
___//起点入队 且 标记起点已访问
while (___) {//当队列不空
___ //取出队头u
for (___) {//枚举graph[u]中 u 的每一个邻接点 v
int v = ___; //邻接点
int w = ___; //边权
//如果 v 未访问
dis[v] = ___; //更新dis[v]
___ //标记 v 已访问 且 入队
}
}
}
int main() {
freopen("___.___", "___", ___);
freopen("___.___", "___", ___);
// 读取输入
cin >> n >> a >> b >> c; //房间数,三人所在房间ABC
for (int i = 1; i < ___; i++) {
int u, v, w;
//输入边,并建图
}
// 计算从A、B、C出发到所有点的距离
bfs(a, disA); //bfs计算A到其他点的距离
bfs(b, ___); //亦然
bfs(c, ___); //亦然
// 寻找最优汇合点
int bestRoom = ___; //最佳房间号初始化
int minSum = ___; //最短距离之和初始化
for (int i = 1; i <= n; i++) {//枚举所有房间,寻找最佳汇合点
___
}
// 输出结果
cout << ___ << ___ << ___ << endl;
return 0;
}
$O(N)$(三次 $BFS/DFS$ 加一次遍历)。 $O(N)$(三个距离数组和三次 $BFS$ 遍历使用的队列)。 四、 总结与反思
通过本题,我深刻理解了树结构在算法设计中的优越性。由于树的特殊性质(任意两点间路径唯一、无环),许多在图论中复杂的问题在树上可以简化为高效的线性或近似线性算法。这提醒我在解决实际问题时,首先要分析数据结构的特性,充分利用特定结构的性质来优化算法设计。特别是 $BFS/DFS$ 在树上的应用,相比通用图的最短路径算法(如 $Dijkstra$)更加高效简洁。 本题可以作为图论入门向进阶过渡的典型题目。它比简单的单源最短路径问题复杂,但又比网络流、强连通分量等高级图论算法简单,非常适合用来训练选手对图论基础知识的综合运用能力,如 $BFS/DFS$ 遍历、邻接表存储、距离计算等核心技能。 本题可以迁移到信息学奥赛中常见的"$树上多源最短路径$"类题目。这类题目通常给定一棵树和多个关键点,要求找到一个点使得所有关键点到该点的距离之和最小或最大。类似的题目包括寻找 $树的中心$、$重心$等 概念相关的问题,都需要计算树上多点间的距离关系。 该问题与运用 $LCA$ 技术解决的题目高度相关。在信息学奥赛中,很多树上路径问题都需要借助 $LCA$ 来高效计算两点间距离。虽然本题可以通过三次 $BFS$ 解决,但对于更大的数据规模或更复杂的需求,通常会使用 $LCA$ 加树上差分等高级技巧来优化计算过程。 五、 推荐题目
题目1241 [NOIP 2010冲刺十三]逃离遗迹
1
评论
2025-10-30 14:03:06
|