P1099 [NOIP2007 提高组] 树网的核

本文介绍了洛谷NOIP2007提高组的一道题目,涉及树网的核(Core)概念,即在给定树网和长度上界s的情况下,找到直径上的一段路径,使得偏心距最小。文章详细解析了模拟、动态规划和最短路等解题方法,包括使用线段树和单调队列等数据结构优化算法。并提供了简洁高效的C++代码实现,证明了在直径上选取路径两端点能得到最小偏心距,从而简化了算法。
摘要由CSDN通过智能技术生成

题目来源

[NOIP2007 提高组] 树网的核 - 洛谷

题目考点

模拟   动态规划,dp   树形结构   枚举,暴力   最短路

题目描述

设 T=(V,E,W) 是一个无圈且连通的无向图(也称为无根树),每条边都有正整数的权,我们称 T 为树网(treenetwork),其中 V,E 分别表示结点与边的集合,W 表示各边长度的集合,并设 T 有 n 个结点。

路径:树网中任何两结点 a,b 都存在唯一的一条简单路径,用 d(a,b) 表示以 a,b 为端点的路径的长度,它是该路径上各边长度之和。我们称 d(a,b) 为 a,b 两结点间的距离。

D(v,P)=min{d(v,u)}, u 为路径 P 上的结点。

树网的直径:树网中最长的路径成为树网的直径。对于给定的树网 T,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。

偏心距 ECC(F):树网 TT 中距路径 FF 最远的结点到路径 FF 的距离,即ECC(F)=max{D(v,F),v∈V}

任务:对于给定的树网 T=(V,E,W) 和非负整数 s,求一个路 F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过 s(可以等于 s),使偏心距 ECC(F) 最小。我们称这个路径为树网 T=(V,E,W) 的核(Core)。必要时,F 可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。

下面的图给出了树网的一个实例。图中,A−B 与 A−C 是两条直径,长度均为 20。点 W 是树网的中心,EF 边的长度为 5。如果指定 s=11,则树网的核为路径DEFG(也可以取为路径DEF),偏心距为 8。如果指定 s=0(或 s=1、s=2),则树网的核为结点 F,偏心距为 12。

输入格式

共 n 行。

第 1 行,两个正整数 n 和 s,中间用一个空格隔开。其中 n 为树网结点的个数,s 为树网的核的长度的上界。设结点编号以此为 1,2…,n。

从第 2 行到第 n 行,每行给出 3 个用空格隔开的正整数 u,v,w,依次表示每一条边的两个端点编号和长度。例如,2 4 7 表示连接结点 2 与 4 的边的长度为 7。

输出格式

一个非负整数,为指定意义下的最小偏心距。

输入输出样例

输入 #1

5 2
1 2 5
2 3 2
2 4 4
2 5 3

输出 #1

5

输入 #2

8 6
1 3 2
2 3 2 
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3

输出 #2

5

说明/提示

  • 对于 40% 的数据,保证 n≤15。
  • 对于 70% 的数据,保证 n≤80。
  • 对于 100% 的数据,保证 n≤300,0≤s≤10^{3},1≤u,v≤n,1≤w≤10^{3}。 

题解

思路:在直径上尺取,计算每条路径的偏心距.

  • 方法1:可以选开始的一条,直接暴力计算每个点到这条点的贡献,之后进行递推,可以发现,只要两端点的改变会使得子树每个节点变化,用什么数据结构可以支持(在线修改+查询最大值)呢?——线段树!!!代码就不出示了......100多行,反正也没人看。

  • 方法2:可以证明一条路径的2个端点最长距离是到直径两个端点,可以用反证法证明,所以只要处理非直径上其他点的贡献即可,预处理。

整理发现,在一个线段上尺取,每一个点有一个贡献,问这些点中最大值是多少?在与两端点贡献比较,单调队列√


然而我们探索并没有结束,我们仔细观察,我们找的最小值肯定要么是两端点到直径端点的贡献,要么是直径上的点的贡献,那么能不能直接取最小呢?答案是可以,可以证明一条路径上,其他点的贡献<两端点到直径端点的贡献(用反证法可以证明)。


比如说染色的是直径,红色的是选的路径,显然,绿色点的贡献都小于路径俩端点的贡献,证明over!


即就算加上其他点的贡献,对这条路径的答案也不会有一点影响,所以单调队列就可以去掉了直接取max就行了。

​
#include<cstdio>
#include<algorithm>
#define M 500005
using namespace std;
int n,m,x,y,z,k,id,top,ans=2e9;
int dis[M],fa[M],head[M];
bool mark[M];
struct edge{
    int to,w,nxt;
}E[M<<1];
void add(int u,int v,int w){
    E[++id]=((edge){v,w,head[u]});
    head[u]=id;//存图
}
void dfs(int f,int x){
    fa[x]=f;
    if(dis[x]>dis[k])k=x;
    for(int i=head[x];i;i=E[i].nxt){
        int y=E[i].to;
        if(y==f||mark[y])continue;
        dis[y]=dis[x]+E[i].w;
        dfs(x,y);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z),add(y,x,z);
    }
    dis[1]=1,dfs(0,1);
    dis[k]=0,dfs(0,k);
    //k表示最远的端点
    top=k;//找直径
    for(int i=top,j=top,l=1,r=0;i;i=fa[i]){
        while(dis[j]-dis[i]>m)j=fa[j];
        //进行尺取,选路径。
        x=max(dis[top]-dis[j],dis[i]);
        //路径两端点到直径端点的最小贡献.
        ans=min(ans,x);
    }
    for(int i=top;i;i=fa[i])mark[i]=1;
    //标记直径,重新计算每个点的贡献。
    for(int i=top;i;i=fa[i]){
        k=i,dis[k]=0;
        dfs(fa[i],i);
    }
    for(int i=1;i<=n;i++)
        ans=max(ans,dis[i]);
    //每个点的贡献,仔细想想为什么是对的。
    printf("%d\n",ans);
    return 0;
    //去掉注释其实代码还挺短的(*^▽^*) 复杂度可以过BZOJ的数据
}

​

 

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值