Welcome to my cnblo|

lzy20091001

园龄:1年3个月粉丝:5关注:2

洛谷 P1099 题解

洛谷 P1099 【NOIP 2007 提高组】 树网的核

题意简述

给定一棵 n 个结点的带边权无根树和一个正整数 s。在这棵树的任意直径上截取一段长度不超过 s 的路径 F,使离 F 最远的点到 F 的距离最小,求出这个距离。

思路

δ(a,b)a,b 之间的路径。

对于任意直径上一点 u,记 d(u)u 不经过直径上其他点所能到达的最远距离。

如图,不妨设一条直径为 δ(a,b),其中的一条路径 F=δ(x,y) 满足 d(x,y)s

引理: 对于直径 δ(a,b) 上任意一点 u,总有 d(u)d(a,u),d(b,u)

证明:假设 d(u)>d(a,u),不妨设 u 不经过直径上其他点所能到达的最远的点是 a(即 d(u)=d(a,u)),则有 d(a,b)=d(a,u)+d(u,b)>d(a,u)+d(u,b)=d(a,b),与 δ(a,b) 是树的直径矛盾。d(u)d(b,u) 同理。

由引理及 ECC 的定义可得出以下性质:

  1. zECC(F) 的贡献是 d(z)

  2. xECC(F) 的贡献是 d(a,x)yECC(F) 的贡献是 d(b,y)​。

由以上二点可得对于 F=δ(x,y)ECC(F)=max{d(a,x),d(b,y),max{d(z)}}

  1. ECC(F)ECC(δ(x,z2)),ECC(δ(y,z1))

    证明:由上式得

    {ECC(F)=max{d(a,x),d(z1),,d(z2),d(b,y)},ECC(δ(x,z2))=max{d(a,x),d(z1),,d(b,z2)},ECC(δ(y,z1))=max{d(a,z1),,d(z2),d(b,y)}.

    由引理知 d(z1)d(a,z1),d(z2)d(b,z2),又易证 d(a,x)<d(a,z1),d(b,y)<d(b,z2),代入得证。

综上,由 1、2 我们得知对于 F=δ(x,y) 如何计算 ECC(F);由 3 归纳得知仅当 d(x,y) 尽可能接近 s 时最优,满足单调性,这提示我们使用双指针。

使用两遍 DFS 找出一条直径 δ(a,b)指定 a 为根结点;在第二次 DFS 时记录每个结点的父节点,记为 fa;求出每个结点到 a 的距离,记为 dis。再使用一遍 DFS 求出直径上每个点的 d。在这条直径上使用双指针枚举 x,y 确定 Fd(a,x)d(b,y) 可以在尺取的过程中用 dis 递推得出,max{d(z)} 可以用单调队列维护,由此求得 ECC(F)min{ECC(F)} 即为答案。

DFS、双指针、单调队列时间复杂度均为 O(n),故总时间复杂度 O(n)

考虑进一步简化。在尺取的过程中求出所有 Fmax{d(a,x),d(b,y)} 的最小值,记为 r;求出所有 d 的最大值,记为 d(t)

F=δ(x,y) 是答案对应的区间。

  • rd(t),则 ECC(F)=max{d(a,x),d(b,y)}=r

  • r<d(t),则一定有 tF 上,即 xty,此时 ECC(F)=d(t)。假设 t 不在 F 上,不妨设 t<x,由引理知 d(t)d(a,t)<d(a,x)ECC(F),一定不如 tF 上优。

所以最终答案为 max{r,d(t)}

考察存在多条直径的情况。题面已经指出直径的中点重合,可以证明每条直径总有一段路径相交,去除直径“分岔”的部分关于这条路径对称,如果 F 包含了直径分岔的结点 m,则 ECC(F)mF 末端的距离,因此选择任何一条直径都不影响结果。具体证明较为繁琐,这里略去。

代码

#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;
}
posted @   lzy20091001  阅读(24)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起