题目链接:https://www.luogu.com.cn/problem/P1099
CSDN食用链接:https://blog.csdn.net/qq_43906000/article/details/107874674
题目描述
设 (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),直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。
偏心距 (mathrm{ECC}(F)):树网 (T) 中距路径 (F) 最远的结点到路径 (F) 的距离,即
(mathrm{ECC}(F)=max{d(v, F),v in V})
任务:对于给定的树网 (T=(V, E, W))和非负整数 (s),求一个路径 (F),他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过 ss(可以等于 (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),中间用一个空格隔开。其中 nn 为树网结点的个数,(s) 为树网的核的长度的上界。设结点编号以此为 (1,2dots,n)
从第 (2) 行到第 (n) 行,每行给出 (3) 个用空格隔开的正整数 (u, v, w),依次表示每一条边的两个端点编号和长度。例如,2 4 7 表示连接结点 (2) 与 (4) 的边的长度为 (7)。
输出格式
一个非负整数,为指定意义下的最小偏心距。
输入输出样例
输入
5 2
1 2 5
2 3 2
2 4 4
2 5 3
输出
5
输入
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3
输出
5
说明/提示
对于 (40\%) 的数据,保证 (n le 15)。
对于 (70\%) 的数据,保证 (n le 80)。
对于 (100\%)的数据,保证 (n le 300,0le sle10^3,1 leq u, v leq n,1 leq w leq 10^3)
emmm,这可能是为数不多的需要翻译的中文题。。。说点阳间的话就是:你需要在树的直径上找一段长为(s)的路径,使得距离(s)最远的的点距离(s)最近。至于点到路径的距离是个什么鬼?实际上也就是点到点的距离,如果s可以覆盖两个点,那么你就需要计算一下离这两个点最远的几个点的距离,然后取个最大值。如果只能覆盖半条边,那么它只能拿端点计算。
那么当什么都不做的时候我们知道树的直径是最长的一条链,所以我们的s需要在这上面取来使得这个距离变小,那么在直径上取路径的话有两种情况,一种是直径的端点离这个路径最远,还有一种情况是非直径的点离这条路径最远。
那么我们先来处理第一种情况,我们知道树的直径的做法可以用两遍dfs求,这样不仅求出了直径的两个端点,还求出了一个端点到所有点的距离(dist[v]),那么我们知道,对于一棵树而言,每个节点的父亲只有一个,那么也就是说我们可以根据(father)从直径的终点来推到直径的起点,既然已经知道了这条链了,那么我们就可以直接尺取了,(i,j)从终点开始,(j)一直往上爬,一旦(dis(i,j)>s)那么(i)就一直往上爬。同时在取的过程中我们取一下他们离直径两个端点最大值的最小值。其代码片段如下:
void dfs(int x,int fa)
{
father[x]=fa;
for (auto v:g[x]){
if (v.first==fa) continue;
dis[v.first]=dis[x]+v.second;
if (dis[v.first]>=d) {d=dis[v.first]; pt=v.first;}
dfs(v.first,x);
}
}
/*********/
dfs(1,-1);
memset(dis,0,sizeof dis);
st=pt;
dfs(pt,-1);
ed=pt;
int ans=inf;
for (int i=ed,j=ed; (i!=-1) && (j!=-1); j=father[j]) {
while (dis[i]-dis[j]>s && i!=-1) i=father[i];
ans=min(ans,max(dis[j],dis[ed]-dis[i]));
}
接下来就是考虑第二种情况了,我们直接取不经过直径离直径上最远的点的最大值就好了,这个答案可以直接对直径上的每个点进行一次dfs就好了,不过在这之前由于不能经过直径,所以我们要对直径上的点打上标记:
int dfs_dis(int x,int fa)
{
int dist=0;
for (auto v:g[x]){
if (v.first==fa) continue;
if (vis[v.first]) continue;
dist=max(dist,dfs_dis(v.first,x)+v.second);
}
return dist;
}
/*************************/
for (int i=ed; i!=-1; i=father[i]) vis[i]=1;
for (int i=ed; i!=-1; i=father[i]) {
int p=dfs_dis(i,-1);
ans=max(p,ans);
}
于是此题就愉快地结束了!!
以下是AC代码:
#include <bits/stdc++.h>
using namespace std;
#define debug printf("@#$#@$2
")
#define mk make_pair
const int mac=1e3+10;
const int inf=1e9+10;
vector<pair<int,int> >g[mac];
int dis[mac],d=0,pt,st,ed;
int father[mac],vis[mac];
void dfs(int x,int fa)
{
father[x]=fa;
for (auto v:g[x]){
if (v.first==fa) continue;
dis[v.first]=dis[x]+v.second;
if (dis[v.first]>=d) {d=dis[v.first]; pt=v.first;}
dfs(v.first,x);
}
}
int dfs_dis(int x,int fa)
{
int dist=0;
for (auto v:g[x]){
if (v.first==fa) continue;
if (vis[v.first]) continue;
dist=max(dist,dfs_dis(v.first,x)+v.second);
}
return dist;
}
int main(int argc, char const *argv[])
{
int n,s;
scanf ("%d%d",&n,&s);
for (int i=1; i<n; i++){
int u,v,w;
scanf ("%d%d%d",&u,&v,&w);
g[u].push_back(mk(v,w)); g[v].push_back(mk(u,w));
}
dfs(1,-1);
memset(dis,0,sizeof dis);
st=pt;
dfs(pt,-1);
ed=pt;
int ans=inf;
for (int i=ed,j=ed; (i!=-1) && (j!=-1); j=father[j]){
while (dis[i]-dis[j]>s && i!=-1) i=father[i];
ans=min(ans,max(dis[j],dis[ed]-dis[i]));
}
for (int i=ed; i!=-1; i=father[i]) vis[i]=1;
for (int i=ed; i!=-1; i=father[i]){
int p=dfs_dis(i,-1);
ans=max(p,ans);
}
printf("%d
",ans);
return 0;
}