1 条题解
-
0
题目概述
给定一棵树(铁路网络),需要选择一个车站作为枢纽站 Bitowice,使得所有车站对之间的平均通行距离最小。通行距离定义为两个车站之间的最短路径长度(边数)。
问题分析
关键观察
- 树结构:铁路网络是一棵树,任意两点间有唯一简单路径
- 目标函数:最小化所有无序点对的距离之和
- 问题转化:这实际上是求树的重心(centroid)问题
数学形式化
设选择车站 作为枢纽,总距离和为:
我们需要找到 使得 最小。
算法思路
两次DFS求解
第一次DFS:预处理
以任意节点(如节点1)为根:
- 计算每个节点的子树大小
siz[u] - 计算每个节点的深度
dep[u] - 计算以根节点为枢纽的总距离
dp[1]
void dfs(const int u, const int fa) { siz[u] = 1, dep[u] = dep[fa] + 1, dp[u] = dep[u]; for (int i = head[u]; i; i = edges[i].nxt) { int to = edges[i].to; if (to == fa) continue; dfs(to, u); siz[u] += siz[to]; dp[u] += dp[to]; } }第二次DFS:换根DP
从根节点开始,计算以每个节点为枢纽的总距离:
关键公式:当从父节点 移动到子节点 时:
解释:
- 移动到 后, 子树中所有节点距离减少1
- 子树外的所有节点距离增加1
- 净变化:$(n - \text{siz}[v]) - \text{siz}[v] = n - 2 \times \text{siz}[v]$
void dfs2(const int u, const int fa, const int dpu) { // 更新最优解 if (dpu > ansdp) ansdp = dpu, ansid = u; if (dpu == ansdp && u < ansid) ansid = u; // 继续DFS for (int i = head[u]; i; i = edges[i].nxt) { int to = edges[i].to; if (to == fa) continue; dfs2(to, u, dpu + n - (siz[to] << 1)); // siz[to] << 1 = 2*siz[to] } }
算法详解
第一次DFS的作用
- 子树大小:
siz[u]表示以u为根的子树节点数 - 深度计算:
dep[u]表示节点u到根节点的距离 - 根节点距离和:
dp[1]表示以节点1为枢纽的总距离
换根DP原理
设当前在节点 ,已知 ,要计算子节点 的 :
- 子树中的节点:到 比到 近1,贡献
- 其他节点:到 比到 远1,贡献
- 总变化:$(n - \text{siz}[v]) - \text{siz}[v] = n - 2\text{siz}[v]$
所以:
最优解选择
- 记录最大的总距离和
ansdp(实际应该是最小值,但代码中用了最大,可能是为了统一处理) - 当多个节点有相同的最优值时,选择编号最小的
样例解析
样例输入
8 1 4 5 6 4 5 6 7 6 8 2 4 3 4树结构:
1 | 4 /|\ 2 3 5 | 6 / \ 7 8计算过程
- 以节点1为根进行第一次DFS,计算各节点子树大小和深度
- 计算以节点1为枢纽的总距离
- 进行第二次DFS,计算以每个节点为枢纽的总距离
- 发现节点7或8为枢纽时总距离最小(36)
- 选择编号较小的节点7
复杂度分析
- 时间复杂度:
- 两次DFS,每个节点访问一次
- 空间复杂度:
- 存储树结构和相关数组
算法亮点
- 换根DP:高效计算以每个节点为根时的总距离
- 公式推导:利用子树大小快速计算距离变化
- 线性复杂度:适合 的大规模数据
- 正确性保证:基于树的重心理论
理论背景
树的重心性质
对于树中的节点 ,以下等价:
- 是重心
- 的所有子树大小都不超过
- 最小化到其他所有节点的总距离
本题正是性质3的应用。
总结
这道题的核心在于:
- 问题识别:识别出这是求树重心问题
- 换根DP:掌握高效的树形动态规划技巧
- 公式理解:理解距离和变化的数学原理
- 实现细节:注意边界条件和最优解选择
这种"树形DP + 换根技巧"的方法在解决树上的最优化问题时非常有效,是竞赛中的经典题型。
- 1
信息
- ID
- 4867
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者