洛谷 P1099 题解
题意简述
给定一棵
思路
记
对于任意直径上一点
如图,不妨设一条直径为
引理: 对于直径
证明:假设
由引理及
-
对 的贡献是 。 -
对 的贡献是 , 对 的贡献是 。
由以上二点可得对于
-
。证明:由上式得
由引理知
,又易证 ,代入得证。
综上,由 1、2 我们得知对于
使用两遍 DFS 找出一条直径
DFS、双指针、单调队列时间复杂度均为
考虑进一步简化。在尺取的过程中求出所有
设
-
若
,则 。 -
若
,则一定有 在 上,即 ,此时 。假设 不在 上,不妨设 ,由引理知 ,一定不如 在 上优。
所以最终答案为
考察存在多条直径的情况。题面已经指出直径的中点重合,可以证明每条直径总有一段路径相交,去除直径“分岔”的部分关于这条路径对称,如果
代码
#include <iostream> #include <vector> struct Edge // 边 { int to, w; // 终点和边权 }; int far; // 用于求直径端点 int fa[305], dis[305]; // fa 为以 a 为根每个点的父节点, 为了省事 dis 和 d' 都用 dis[] 来存储 bool diameter[305]; // 是否在直径上 std::vector<Edge> mp[305]; // 存树 void dfs(int u, int f) // 为了省事, 求直径, dis, d' 全部放进了一个函数里 { fa[u] = f; if (dis[u] > dis[far]) far = u; // 求直径的端点 for (auto i : mp[u]) if (i.to != f && !diameter[i.to]) // i.to != f 避免死循环, !diameter[i.to] 是 d' 的定义 { dis[i.to] = dis[u] + i.w; dfs(i.to, u); } } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); int n, s, ans = 300000; int a, b; // 直径的端点 std::cin >> n >> s; for (int i = 1; i < n; i++) // 建图 { int u, v, w; std::cin >> u >> v >> w; mp[u].push_back({v, w}); mp[v].push_back({u, w}); } // 求直径 dfs(1, 0); a = far; dis[a] = 0; dfs(a, 0); b = far; for (int x = b, y = b; x >= 1; x = fa[x]) // 双指针 { while (dis[y] - dis[x] > s) // 性质 3, 仅当 d(x, y) 尽可能接近 s 时最优 y = fa[y]; ans = std::min(ans, std::max(dis[b] - dis[y], dis[x])); // 求所有 F 的 max{d(a, x), d(b, y)} 的最小值 } for (int i = b; i >= 1; i = fa[i]) // 标记直径, 为求 d' 做准备 diameter[i] = true; for (int i = b; i >= 1; i = fa[i]) // 求 d' { dis[i] = 0; dfs(i, fa[i]); } for (int i = 1; i <= n; i++) // 求 d'(t) ans = std::max(ans, dis[i]); std::cout << ans << "\n"; return 0; }
本文来自博客园,作者:lzy20091001,转载请注明原文链接:https://www.cnblogs.com/lzy20091001/p/18063883/Luogu-P1099-solution
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步