洛谷P1099 树网的核 \color{green}{\text{洛谷P1099\ \ \ \ \ 树网的核}} 洛谷P1099 树网的核
【题目大意】:
\color{blue}{\text{【题目大意】:}}
【题目大意】:
【思路】:
\color{blue}{\text{【思路】:}}
【思路】: 首先,我们需要求出这棵树的直径。如何求呢?有一个很好实现,且时间复杂度仅为
O
(
n
)
O(n)
O(n) 的算法。如下所述。
首先,我们随便选一个点 t t t,然后找出离它最最远的点 u u u,它一定是树的一个直径的一端。所以,我们再找出离 u u u 最远的点 v v v,路径 ( u , v ) (u,v) (u,v) 就是树的一个直径。
第二步显然,但第一步不是很好理解。这里简单给大家讲讲。
如图,假设
3
3
3 是离
1
1
1 最远的点,那么
3
3
3 一定是树的一条直径的一端。为什么?如果
3
3
3 不是直径的一端,假设直径的那端是
4
4
4,那么
(
1
,
4
)
(1,4)
(1,4) 一定比
(
1
,
3
)
(1,3)
(1,3) 长,那么
3
3
3 就不是离
1
1
1 最远的点了。
接下来,我们考虑如何求出答案。首先,答案有两种情况:
- 答案是某个点到直径两端的距离。
- 答案是某个点到非直径端点的距离。
为什么呢?因为如果答案是直径端点到我们选出线段的距离肯定比直径上的非直径端点的点远 (好拗口啊,大家多读读)。
我们先求出第一种情况的答案。这点比较简单,直接用尺取法即可。毕竟在所选线段长度不超过 s s s 时,肯定是越长越好。
对于第二种情况。它只会发生在线段是一个点的情况。为什么?因为如果这个线段不止 1 1 1 个点,那么记 u u u 是线段上离那个非直径上点最近的点,对于线段上其它点 v v v,它到那个非直径上点的距离肯定 > u >u >u 到那个点的距离。这种情况下,我们反而比如让它退化。
于是,我们的思路就很清楚了。首先求出树的直径。然后在树的直径上尺取,求出候选答案 t 1 t_1 t1。最后求出每个非直径点到直径点的最短距离,记其最大值为 t 2 t_2 t2。 max { t 1 , t 2 } \max \{t_1,t_2 \} max{t1,t2} 就是答案。
为什么是最大值?因为无论我们怎么选,答案都不会小于
t
1
t_1
t1,也不会小于
t
2
t_2
t2,即最小答案就是
max
{
t
1
,
t
2
}
\max \{ t_1,t_2 \}
max{t1,t2}。假设让我们得到
t
2
t_2
t2 的点为
u
u
u,离它最近的直径上的点为
v
v
v,那么我们在线段包括
v
v
v 时,我们就要考虑
t
2
t_2
t2,否则一定不用考虑(因为直径端点到它的距离一定大于
t
2
t_2
t2。为什么?这就像上面的图一样的情况了,
3
3
3 就是得到
t
2
t_2
t2 的点,
1
,
2
,
4
1,2,4
1,2,4 就是直径上的点,
2
2
2 就是那个
v
v
v,剩下的自己思考)。
【代码】: \color{blue}{\text{【代码】:}} 【代码】:
struct edge{
int next,to,len;
}e[610];int h[310],tot;
inline void add(int a,int b,int c){
e[++tot]=(edge){h[a],b,c};h[a]=tot;
}
bool visit[310];
int dis[310],f[310];
void dfs(int u,int fa){
f[u]=fa;//记录每个点的父亲
for(int i=h[u];i;i=e[i].next){
register int to=e[i].to;
if (visit[to]) continue;
if (to==fa) continue;
dis[to]=dis[u]+e[i].len;
dfs(to,u);//递归求解
}
}
int n,s,ans=2e9,u,v;
inline int solve(int u){
dis[u]=0;dfs(u,0);
register int res=0;
for(int i=1;i<=n;i++)
if (dis[i]>dis[res])
res=i;
return res;
}
int main(){
scanf("%d%d",&n,&s);u=v=0;
for(int i=1,a,b,c;i<=n;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);add(b,a,c);
}
v=solve(u=solve(1));
// (u,v)就是该树的直径
for(int i=v,j=v;i;i=f[i]){
while (dis[j]-dis[i]>s) j=f[j];
ans=min(ans,max(dis[v]-dis[j],dis[i]));
}//在直径上进行尺取(在长度不超限制时,越长越好)
for(int i=v;i;i=f[i])
visit[i]=true;
for(int i=v;i;i=f[i]){
dis[i]=0;dfs(i,f[i]);
}//重新搜索,不搜索直径上点,就可以求出每个点离直径上的点的最短路径,大家画个图就可以理解了。
for(int i=1;i<=n;i++)
ans=max(ans,dis[i]);//情况2
printf("%d",ans);
return 0;
}